Skip to content

Commit 8dc6a37

Browse files
barrbrainjrn
authored andcommitted
fast-import: add 'ls' command
Lazy fast-import frontend authors that want to rely on the backend to keep track of the content of the imported trees _almost_ have what they need in the 'cat-blob' command (v1.7.4-rc0~30^2~3, 2010-11-28). But it is not quite enough, since (1) cat-blob can be used to retrieve the content of files, but not their mode, and (2) using cat-blob requires the frontend to keep track of a name (mark number or object id) for each blob to be retrieved Introduce an 'ls' command to complement cat-blob and take care of the remaining needs. The 'ls' command finds what is at a given path within a given tree-ish (tag, commit, or tree): 'ls' SP <dataref> SP <path> LF or in fast-import's active commit: 'ls' SP <path> LF The response is a single line sent through the cat-blob channel, imitating ls-tree output. So for example: FE> ls :1 Documentation gfi> 040000 tree 9e6c2b599341d28a2a375f8207507e0a2a627fe9 Documentation FE> ls 9e6c2b599341d28a2a375f8207507e0a2a627fe9 git-fast-import.txt gfi> 100644 blob 4f92954396e3f0f97e75b6838a5635b583708870 git-fast-import.txt FE> ls :1 RelNotes gfi> 120000 blob b942e49 RelNotes FE> cat-blob b942e49 gfi> b942e49 blob 32 gfi> Documentation/RelNotes/1.7.4.txt The most interesting parts of the reply are the first word, which is a 6-digit octal mode (regular file, executable, symlink, directory, or submodule), and the part from the second space to the tab, which is a <dataref> that can be used in later cat-blob, ls, and filemodify (M) commands to refer to the content (blob, tree, or commit) at that path. If there is nothing there, the response is "missing some/path". The intent is for this command to be used to read files from the active commit, so a frontend can apply patches to them, and to copy files and directories from previous revisions. For example, proposed updates to svn-fe use this command in place of its internal representation of the repository directory structure. This simplifies the frontend a great deal and means support for resuming an import in a separate fast-import run (i.e., incremental import) is basically free. Signed-off-by: David Barr <[email protected]> Signed-off-by: Jonathan Nieder <[email protected]> Improved-by: Junio C Hamano <[email protected]> Improved-by: Sverre Rabbelier <[email protected]>
1 parent 046613c commit 8dc6a37

File tree

3 files changed

+303
-14
lines changed

3 files changed

+303
-14
lines changed

Documentation/git-fast-import.txt

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -196,7 +196,8 @@ especially when a higher level language such as Perl, Python or
196196
Ruby is being used.
197197

198198
fast-import is very strict about its input. Where we say SP below we mean
199-
*exactly* one space. Likewise LF means one (and only one) linefeed.
199+
*exactly* one space. Likewise LF means one (and only one) linefeed
200+
and HT one (and only one) horizontal tab.
200201
Supplying additional whitespace characters will cause unexpected
201202
results, such as branch names or file names with leading or trailing
202203
spaces in their name, or early termination of fast-import when it encounters
@@ -334,6 +335,11 @@ and control the current import process. More detailed discussion
334335
format to the file descriptor set with `--cat-blob-fd` or
335336
`stdout` if unspecified.
336337

338+
`ls`::
339+
Causes fast-import to print a line describing a directory
340+
entry in 'ls-tree' format to the file descriptor set with
341+
`--cat-blob-fd` or `stdout` if unspecified.
342+
337343
`feature`::
338344
Require that fast-import supports the specified feature, or
339345
abort if it does not.
@@ -919,6 +925,55 @@ This command can be used anywhere in the stream that comments are
919925
accepted. In particular, the `cat-blob` command can be used in the
920926
middle of a commit but not in the middle of a `data` command.
921927

928+
`ls`
929+
~~~~
930+
Prints information about the object at a path to a file descriptor
931+
previously arranged with the `--cat-blob-fd` argument. This allows
932+
printing a blob from the active commit (with `cat-blob`) or copying a
933+
blob or tree from a previous commit for use in the current one (with
934+
`filemodify`).
935+
936+
The `ls` command can be used anywhere in the stream that comments are
937+
accepted, including the middle of a commit.
938+
939+
Reading from the active commit::
940+
This form can only be used in the middle of a `commit`.
941+
The path names a directory entry within fast-import's
942+
active commit. The path must be quoted in this case.
943+
+
944+
....
945+
'ls' SP <path> LF
946+
....
947+
948+
Reading from a named tree::
949+
The `<dataref>` can be a mark reference (`:<idnum>`) or the
950+
full 40-byte SHA-1 of a Git tag, commit, or tree object,
951+
preexisting or waiting to be written.
952+
The path is relative to the top level of the tree
953+
named by `<dataref>`.
954+
+
955+
....
956+
'ls' SP <dataref> SP <path> LF
957+
....
958+
959+
See `filemodify` above for a detailed description of `<path>`.
960+
961+
Output uses the same format as `git ls-tree <tree> {litdd} <path>`:
962+
963+
====
964+
<mode> SP ('blob' | 'tree' | 'commit') SP <dataref> HT <path> LF
965+
====
966+
967+
The <dataref> represents the blob, tree, or commit object at <path>
968+
and can be used in later 'cat-blob', 'filemodify', or 'ls' commands.
969+
970+
If there is no file or subtree at that path, 'git fast-import' will
971+
instead report
972+
973+
====
974+
missing SP <path> LF
975+
====
976+
922977
`feature`
923978
~~~~~~~~~
924979
Require that fast-import supports the specified feature, or abort if
@@ -946,8 +1001,10 @@ import-marks::
9461001
any "feature import-marks" command in the stream.
9471002

9481003
cat-blob::
949-
Ignored. Versions of fast-import not supporting the
950-
"cat-blob" command will exit with a message indicating so.
1004+
ls::
1005+
Require that the backend support the 'cat-blob' or 'ls' command.
1006+
Versions of fast-import not supporting the specified command
1007+
will exit with a message indicating so.
9511008
This lets the import error out early with a clear message,
9521009
rather than wasting time on the early part of an import
9531010
before the unsupported command is detected.

fast-import.c

Lines changed: 159 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,12 @@ Format of STDIN stream:
2424
commit_msg
2525
('from' sp committish lf)?
2626
('merge' sp committish lf)*
27-
file_change*
27+
(file_change | ls)*
2828
lf?;
2929
commit_msg ::= data;
3030
31+
ls ::= 'ls' sp '"' quoted(path) '"' lf;
32+
3133
file_change ::= file_clr
3234
| file_del
3335
| file_rnm
@@ -132,7 +134,7 @@ Format of STDIN stream:
132134
ts ::= # time since the epoch in seconds, ascii base10 notation;
133135
tz ::= # GIT style timezone;
134136
135-
# note: comments and cat requests may appear anywhere
137+
# note: comments, ls and cat requests may appear anywhere
136138
# in the input, except within a data command. Any form
137139
# of the data command always escapes the related input
138140
# from comment processing.
@@ -141,7 +143,9 @@ Format of STDIN stream:
141143
# must be the first character on that line (an lf
142144
# preceded it).
143145
#
146+
144147
cat_blob ::= 'cat-blob' sp (hexsha1 | idnum) lf;
148+
ls_tree ::= 'ls' sp (hexsha1 | idnum) sp path_str lf;
145149
146150
comment ::= '#' not_lf* lf;
147151
not_lf ::= # Any byte that is not ASCII newline (LF);
@@ -374,6 +378,7 @@ static int cat_blob_fd = STDOUT_FILENO;
374378

375379
static void parse_argv(void);
376380
static void parse_cat_blob(void);
381+
static void parse_ls(struct branch *b);
377382

378383
static void write_branch_report(FILE *rpt, struct branch *b)
379384
{
@@ -2614,6 +2619,8 @@ static void parse_new_commit(void)
26142619
note_change_n(b, prev_fanout);
26152620
else if (!strcmp("deleteall", command_buf.buf))
26162621
file_change_deleteall(b);
2622+
else if (!prefixcmp(command_buf.buf, "ls "))
2623+
parse_ls(b);
26172624
else {
26182625
unread_command_buf = 1;
26192626
break;
@@ -2837,6 +2844,153 @@ static void parse_cat_blob(void)
28372844
cat_blob(oe, sha1);
28382845
}
28392846

2847+
static struct object_entry *dereference(struct object_entry *oe,
2848+
unsigned char sha1[20])
2849+
{
2850+
unsigned long size;
2851+
void *buf = NULL;
2852+
if (!oe) {
2853+
enum object_type type = sha1_object_info(sha1, NULL);
2854+
if (type < 0)
2855+
die("object not found: %s", sha1_to_hex(sha1));
2856+
/* cache it! */
2857+
oe = insert_object(sha1);
2858+
oe->type = type;
2859+
oe->pack_id = MAX_PACK_ID;
2860+
oe->idx.offset = 1;
2861+
}
2862+
switch (oe->type) {
2863+
case OBJ_TREE: /* easy case. */
2864+
return oe;
2865+
case OBJ_COMMIT:
2866+
case OBJ_TAG:
2867+
break;
2868+
default:
2869+
die("Not a treeish: %s", command_buf.buf);
2870+
}
2871+
2872+
if (oe->pack_id != MAX_PACK_ID) { /* in a pack being written */
2873+
buf = gfi_unpack_entry(oe, &size);
2874+
} else {
2875+
enum object_type unused;
2876+
buf = read_sha1_file(sha1, &unused, &size);
2877+
}
2878+
if (!buf)
2879+
die("Can't load object %s", sha1_to_hex(sha1));
2880+
2881+
/* Peel one layer. */
2882+
switch (oe->type) {
2883+
case OBJ_TAG:
2884+
if (size < 40 + strlen("object ") ||
2885+
get_sha1_hex(buf + strlen("object "), sha1))
2886+
die("Invalid SHA1 in tag: %s", command_buf.buf);
2887+
break;
2888+
case OBJ_COMMIT:
2889+
if (size < 40 + strlen("tree ") ||
2890+
get_sha1_hex(buf + strlen("tree "), sha1))
2891+
die("Invalid SHA1 in commit: %s", command_buf.buf);
2892+
}
2893+
2894+
free(buf);
2895+
return find_object(sha1);
2896+
}
2897+
2898+
static struct object_entry *parse_treeish_dataref(const char **p)
2899+
{
2900+
unsigned char sha1[20];
2901+
struct object_entry *e;
2902+
2903+
if (**p == ':') { /* <mark> */
2904+
char *endptr;
2905+
e = find_mark(strtoumax(*p + 1, &endptr, 10));
2906+
if (endptr == *p + 1)
2907+
die("Invalid mark: %s", command_buf.buf);
2908+
if (!e)
2909+
die("Unknown mark: %s", command_buf.buf);
2910+
*p = endptr;
2911+
hashcpy(sha1, e->idx.sha1);
2912+
} else { /* <sha1> */
2913+
if (get_sha1_hex(*p, sha1))
2914+
die("Invalid SHA1: %s", command_buf.buf);
2915+
e = find_object(sha1);
2916+
*p += 40;
2917+
}
2918+
2919+
while (!e || e->type != OBJ_TREE)
2920+
e = dereference(e, sha1);
2921+
return e;
2922+
}
2923+
2924+
static void print_ls(int mode, const unsigned char *sha1, const char *path)
2925+
{
2926+
static struct strbuf line = STRBUF_INIT;
2927+
2928+
/* See show_tree(). */
2929+
const char *type =
2930+
S_ISGITLINK(mode) ? commit_type :
2931+
S_ISDIR(mode) ? tree_type :
2932+
blob_type;
2933+
2934+
if (!mode) {
2935+
/* missing SP path LF */
2936+
strbuf_reset(&line);
2937+
strbuf_addstr(&line, "missing ");
2938+
quote_c_style(path, &line, NULL, 0);
2939+
strbuf_addch(&line, '\n');
2940+
} else {
2941+
/* mode SP type SP object_name TAB path LF */
2942+
strbuf_reset(&line);
2943+
strbuf_addf(&line, "%06o %s %s\t",
2944+
mode, type, sha1_to_hex(sha1));
2945+
quote_c_style(path, &line, NULL, 0);
2946+
strbuf_addch(&line, '\n');
2947+
}
2948+
cat_blob_write(line.buf, line.len);
2949+
}
2950+
2951+
static void parse_ls(struct branch *b)
2952+
{
2953+
const char *p;
2954+
struct tree_entry *root = NULL;
2955+
struct tree_entry leaf = {0};
2956+
2957+
/* ls SP (<treeish> SP)? <path> */
2958+
p = command_buf.buf + strlen("ls ");
2959+
if (*p == '"') {
2960+
if (!b)
2961+
die("Not in a commit: %s", command_buf.buf);
2962+
root = &b->branch_tree;
2963+
} else {
2964+
struct object_entry *e = parse_treeish_dataref(&p);
2965+
root = new_tree_entry();
2966+
hashcpy(root->versions[1].sha1, e->idx.sha1);
2967+
load_tree(root);
2968+
if (*p++ != ' ')
2969+
die("Missing space after tree-ish: %s", command_buf.buf);
2970+
}
2971+
if (*p == '"') {
2972+
static struct strbuf uq = STRBUF_INIT;
2973+
const char *endp;
2974+
strbuf_reset(&uq);
2975+
if (unquote_c_style(&uq, p, &endp))
2976+
die("Invalid path: %s", command_buf.buf);
2977+
if (*endp)
2978+
die("Garbage after path in: %s", command_buf.buf);
2979+
p = uq.buf;
2980+
}
2981+
tree_content_get(root, p, &leaf);
2982+
/*
2983+
* A directory in preparation would have a sha1 of zero
2984+
* until it is saved. Save, for simplicity.
2985+
*/
2986+
if (S_ISDIR(leaf.versions[1].mode))
2987+
store_tree(&leaf);
2988+
2989+
print_ls(leaf.versions[1].mode, leaf.versions[1].sha1, p);
2990+
if (!b || root != &b->branch_tree)
2991+
release_tree_entry(root);
2992+
}
2993+
28402994
static void checkpoint(void)
28412995
{
28422996
checkpoint_requested = 0;
@@ -3001,7 +3155,7 @@ static int parse_one_feature(const char *feature, int from_stream)
30013155
relative_marks_paths = 0;
30023156
} else if (!prefixcmp(feature, "force")) {
30033157
force_update = 1;
3004-
} else if (!strcmp(feature, "notes")) {
3158+
} else if (!strcmp(feature, "notes") || !strcmp(feature, "ls")) {
30053159
; /* do nothing; we have the feature */
30063160
} else {
30073161
return 0;
@@ -3142,6 +3296,8 @@ int main(int argc, const char **argv)
31423296
while (read_next_command() != EOF) {
31433297
if (!strcmp("blob", command_buf.buf))
31443298
parse_new_blob();
3299+
else if (!prefixcmp(command_buf.buf, "ls "))
3300+
parse_ls(NULL);
31453301
else if (!prefixcmp(command_buf.buf, "commit "))
31463302
parse_new_commit();
31473303
else if (!prefixcmp(command_buf.buf, "tag "))

0 commit comments

Comments
 (0)