| new_tag
| reset_branch
| checkpoint
+ | progress
;
new_blob ::= 'blob' lf
- mark?
+ mark?
file_content;
file_content ::= data;
('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
('merge' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)*
file_change*
- lf;
+ lf?;
commit_msg ::= data;
- file_change ::= file_clr | file_del | file_obm | file_inm;
+ file_change ::= file_clr
+ | file_del
+ | file_rnm
+ | file_cpy
+ | file_obm
+ | file_inm;
file_clr ::= 'deleteall' lf;
file_del ::= 'D' sp path_str lf;
+ file_rnm ::= 'R' sp path_str sp path_str lf;
+ file_cpy ::= 'C' sp path_str sp path_str lf;
file_obm ::= 'M' sp mode sp (hexsha1 | idnum) sp path_str lf;
file_inm ::= 'M' sp mode sp 'inline' sp path_str lf
data;
new_tag ::= 'tag' sp tag_str lf
'from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf
- 'tagger' sp name '<' email '>' when lf
+ 'tagger' sp name '<' email '>' when lf
tag_msg;
tag_msg ::= data;
reset_branch ::= 'reset' sp ref_str lf
('from' sp (ref_str | hexsha1 | sha1exp_str | idnum) lf)?
- lf;
+ lf?;
checkpoint ::= 'checkpoint' lf
- lf;
+ lf?;
+
+ progress ::= 'progress' sp not_lf* lf
+ lf?;
# note: the first idnum in a stream should be 1 and subsequent
# idnums should not have gaps between values as this will cause
# the stream parser to reserve space for the gapped values. An
- # idnum can be updated in the future to a new object by issuing
+ # idnum can be updated in the future to a new object by issuing
# a new mark directive with the old idnum.
- #
+ #
mark ::= 'mark' sp idnum lf;
data ::= (delimited_data | exact_data)
- lf;
+ lf?;
# note: delim may be any string but must not contain lf.
# data_line may contain any data but must not be exactly
# delim.
delimited_data ::= 'data' sp '<<' delim lf
(data_line lf)*
- delim lf;
+ delim lf;
# note: declen indicates the length of binary_data in bytes.
# declen does not include the lf preceeding the binary data.
# note: quoted strings are C-style quoting supporting \c for
# common escapes of 'c' (e..g \n, \t, \\, \") or \nnn where nnn
- # is the signed byte value in octal. Note that the only
+ # is the signed byte value in octal. Note that the only
# characters which must actually be escaped to protect the
# stream formatting is: \, " and LF. Otherwise these values
- # are UTF8.
+ # are UTF8.
#
ref_str ::= ref;
sha1exp_str ::= sha1exp;
lf ::= # ASCII newline (LF) character;
# note: a colon (':') must precede the numerical value assigned to
- # an idnum. This is to distinguish it from a ref or tag name as
+ # an idnum. This is to distinguish it from a ref or tag name as
# GIT does not permit ':' in ref or tag strings.
- #
+ #
idnum ::= ':' bigint;
path ::= # GIT style file path, e.g. "a/b/c";
ref ::= # GIT ref name, e.g. "refs/heads/MOZ_GECKO_EXPERIMENT";
hexsha1 ::= # SHA1 in hexadecimal format;
# note: name and email are UTF8 strings, however name must not
- # contain '<' or lf and email must not contain any of the
+ # contain '<' or lf and email must not contain any of the
# following: '<', '>', lf.
- #
+ #
name ::= # valid GIT author/committer name;
email ::= # valid GIT author/committer email;
ts ::= # time since the epoch in seconds, ascii base10 notation;
tz ::= # GIT style timezone;
+
+ # note: comments may appear anywhere in the input, except
+ # within a data command. Any form of the data command
+ # always escapes the related input from comment processing.
+ #
+ # In case it is not clear, the '#' that starts the comment
+ # must be the first character on that the line (an lf have
+ # preceeded it).
+ #
+ comment ::= '#' not_lf* lf;
+ not_lf ::= # Any byte that is not ASCII newline (LF);
*/
#include "builtin.h"
WHENSPEC_NOW,
} whenspec_type;
+struct recent_command
+{
+ struct recent_command *prev;
+ struct recent_command *next;
+ char *buf;
+};
+
/* Configured limits on output */
static unsigned long max_depth = 10;
static off_t max_packsize = (1LL << 32) - 1;
/* Input stream parsing */
static whenspec_type whenspec = WHENSPEC_RAW;
static struct strbuf command_buf;
+static int unread_command_buf;
+static struct recent_command cmd_hist = {&cmd_hist, &cmd_hist, NULL};
+static struct recent_command *cmd_tail = &cmd_hist;
+static struct recent_command *rc_free;
+static unsigned int cmd_save = 100;
static uintmax_t next_mark;
static struct dbuf new_data;
+static void write_branch_report(FILE *rpt, struct branch *b)
+{
+ fprintf(rpt, "%s:\n", b->name);
+
+ fprintf(rpt, " status :");
+ if (b->active)
+ fputs(" active", rpt);
+ if (b->branch_tree.tree)
+ fputs(" loaded", rpt);
+ if (is_null_sha1(b->branch_tree.versions[1].sha1))
+ fputs(" dirty", rpt);
+ fputc('\n', rpt);
+
+ fprintf(rpt, " tip commit : %s\n", sha1_to_hex(b->sha1));
+ fprintf(rpt, " old tree : %s\n", sha1_to_hex(b->branch_tree.versions[0].sha1));
+ fprintf(rpt, " cur tree : %s\n", sha1_to_hex(b->branch_tree.versions[1].sha1));
+ fprintf(rpt, " commit clock: %" PRIuMAX "\n", b->last_commit);
+
+ fputs(" last pack : ", rpt);
+ if (b->pack_id < MAX_PACK_ID)
+ fprintf(rpt, "%u", b->pack_id);
+ fputc('\n', rpt);
+
+ fputc('\n', rpt);
+}
+
+static void write_crash_report(const char *err)
+{
+ char *loc = git_path("fast_import_crash_%d", getpid());
+ FILE *rpt = fopen(loc, "w");
+ struct branch *b;
+ unsigned long lu;
+ struct recent_command *rc;
+
+ if (!rpt) {
+ error("can't write crash report %s: %s", loc, strerror(errno));
+ return;
+ }
+
+ fprintf(stderr, "fast-import: dumping crash report to %s\n", loc);
+
+ fprintf(rpt, "fast-import crash report:\n");
+ fprintf(rpt, " fast-import process: %d\n", getpid());
+ fprintf(rpt, " parent process : %d\n", getppid());
+ fprintf(rpt, " at %s\n", show_date(time(NULL), 0, DATE_LOCAL));
+ fputc('\n', rpt);
+
+ fputs("fatal: ", rpt);
+ fputs(err, rpt);
+ fputc('\n', rpt);
+
+ fputc('\n', rpt);
+ fputs("Most Recent Commands Before Crash\n", rpt);
+ fputs("---------------------------------\n", rpt);
+ for (rc = cmd_hist.next; rc != &cmd_hist; rc = rc->next) {
+ if (rc->next == &cmd_hist)
+ fputs("* ", rpt);
+ else
+ fputs(" ", rpt);
+ fputs(rc->buf, rpt);
+ fputc('\n', rpt);
+ }
+
+ fputc('\n', rpt);
+ fputs("Active Branch LRU\n", rpt);
+ fputs("-----------------\n", rpt);
+ fprintf(rpt, " active_branches = %lu cur, %lu max\n",
+ cur_active_branches,
+ max_active_branches);
+ fputc('\n', rpt);
+ fputs(" pos clock name\n", rpt);
+ fputs(" ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~\n", rpt);
+ for (b = active_branches, lu = 0; b; b = b->active_next_branch)
+ fprintf(rpt, " %2lu) %6" PRIuMAX" %s\n",
+ ++lu, b->last_commit, b->name);
+
+ fputc('\n', rpt);
+ fputs("Inactive Branches\n", rpt);
+ fputs("-----------------\n", rpt);
+ for (lu = 0; lu < branch_table_sz; lu++) {
+ for (b = branch_table[lu]; b; b = b->table_next_branch)
+ write_branch_report(rpt, b);
+ }
+
+ fputc('\n', rpt);
+ fputs("-------------------\n", rpt);
+ fputs("END OF CRASH REPORT\n", rpt);
+ fclose(rpt);
+}
+
+static NORETURN void die_nicely(const char *err, va_list params)
+{
+ static int zombie;
+ char message[2 * PATH_MAX];
+
+ vsnprintf(message, sizeof(message), err, params);
+ fputs("fatal: ", stderr);
+ fputs(message, stderr);
+ fputc('\n', stderr);
+
+ if (!zombie) {
+ zombie = 1;
+ write_crash_report(message);
+ }
+ exit(128);
+}
static void alloc_objects(unsigned int cnt)
{
if (b)
die("Invalid attempt to create duplicate branch: %s", name);
- if (check_ref_format(name))
+ switch (check_ref_format(name)) {
+ case 0: break; /* its valid */
+ case -2: break; /* valid, but too few '/', allow anyway */
+ default:
die("Branch name doesn't conform to GIT standards: %s", name);
+ }
b = pool_calloc(1, sizeof(struct branch));
b->name = pool_strdup(name);
avail_tree_entry = e;
}
+static struct tree_content *dup_tree_content(struct tree_content *s)
+{
+ struct tree_content *d;
+ struct tree_entry *a, *b;
+ unsigned int i;
+
+ if (!s)
+ return NULL;
+ d = new_tree_content(s->entry_count);
+ for (i = 0; i < s->entry_count; i++) {
+ a = s->entries[i];
+ b = new_tree_entry();
+ memcpy(b, a, sizeof(*a));
+ if (a->tree && is_null_sha1(b->versions[1].sha1))
+ b->tree = dup_tree_content(a->tree);
+ else
+ b->tree = NULL;
+ d->entries[i] = b;
+ }
+ d->entry_count = s->entry_count;
+ d->delta_depth = s->delta_depth;
+
+ return d;
+}
+
static void start_packfile(void)
{
static char tmpfile[PATH_MAX];
snprintf(tmpfile, sizeof(tmpfile),
"%s/tmp_pack_XXXXXX", get_object_directory());
- pack_fd = mkstemp(tmpfile);
- if (pack_fd < 0)
- die("Can't create %s: %s", tmpfile, strerror(errno));
+ pack_fd = xmkstemp(tmpfile);
p = xcalloc(1, sizeof(*p) + strlen(tmpfile) + 2);
strcpy(p->pack_name, tmpfile);
p->pack_fd = pack_fd;
snprintf(tmpfile, sizeof(tmpfile),
"%s/tmp_idx_XXXXXX", get_object_directory());
- idx_fd = mkstemp(tmpfile);
- if (idx_fd < 0)
- die("Can't create %s: %s", tmpfile, strerror(errno));
+ idx_fd = xmkstemp(tmpfile);
f = sha1fd(idx_fd, tmpfile);
sha1write(f, array, 256 * sizeof(int));
SHA1_Init(&ctx);
struct tree_entry *root,
const char *p,
const unsigned char *sha1,
- const uint16_t mode)
+ const uint16_t mode,
+ struct tree_content *subtree)
{
struct tree_content *t = root->tree;
const char *slash1;
n = strlen(p);
if (!n)
die("Empty path component found in input");
+ if (!slash1 && !S_ISDIR(mode) && subtree)
+ die("Non-directories cannot have subtrees");
for (i = 0; i < t->entry_count; i++) {
e = t->entries[i];
if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
if (!slash1) {
- if (e->versions[1].mode == mode
+ if (!S_ISDIR(mode)
+ && e->versions[1].mode == mode
&& !hashcmp(e->versions[1].sha1, sha1))
return 0;
e->versions[1].mode = mode;
hashcpy(e->versions[1].sha1, sha1);
- if (e->tree) {
+ if (e->tree)
release_tree_content_recursive(e->tree);
- e->tree = NULL;
- }
+ e->tree = subtree;
hashclr(root->versions[1].sha1);
return 1;
}
}
if (!e->tree)
load_tree(e);
- if (tree_content_set(e, slash1 + 1, sha1, mode)) {
+ if (tree_content_set(e, slash1 + 1, sha1, mode, subtree)) {
hashclr(root->versions[1].sha1);
return 1;
}
if (slash1) {
e->tree = new_tree_content(8);
e->versions[1].mode = S_IFDIR;
- tree_content_set(e, slash1 + 1, sha1, mode);
+ tree_content_set(e, slash1 + 1, sha1, mode, subtree);
} else {
- e->tree = NULL;
+ e->tree = subtree;
e->versions[1].mode = mode;
hashcpy(e->versions[1].sha1, sha1);
}
return 1;
}
-static int tree_content_remove(struct tree_entry *root, const char *p)
+static int tree_content_remove(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *backup_leaf)
{
struct tree_content *t = root->tree;
const char *slash1;
goto del_entry;
if (!e->tree)
load_tree(e);
- if (tree_content_remove(e, slash1 + 1)) {
+ if (tree_content_remove(e, slash1 + 1, backup_leaf)) {
for (n = 0; n < e->tree->entry_count; n++) {
if (e->tree->entries[n]->versions[1].mode) {
hashclr(root->versions[1].sha1);
return 1;
}
}
+ backup_leaf = NULL;
goto del_entry;
}
return 0;
return 0;
del_entry:
- if (e->tree) {
+ if (backup_leaf)
+ memcpy(backup_leaf, e, sizeof(*backup_leaf));
+ else if (e->tree)
release_tree_content_recursive(e->tree);
- e->tree = NULL;
- }
+ e->tree = NULL;
e->versions[1].mode = 0;
hashclr(e->versions[1].sha1);
hashclr(root->versions[1].sha1);
return 1;
}
+static int tree_content_get(
+ struct tree_entry *root,
+ const char *p,
+ struct tree_entry *leaf)
+{
+ struct tree_content *t = root->tree;
+ const char *slash1;
+ unsigned int i, n;
+ struct tree_entry *e;
+
+ slash1 = strchr(p, '/');
+ if (slash1)
+ n = slash1 - p;
+ else
+ n = strlen(p);
+
+ for (i = 0; i < t->entry_count; i++) {
+ e = t->entries[i];
+ if (e->name->str_len == n && !strncmp(p, e->name->str_dat, n)) {
+ if (!slash1) {
+ memcpy(leaf, e, sizeof(*leaf));
+ if (e->tree && is_null_sha1(e->versions[1].sha1))
+ leaf->tree = dup_tree_content(e->tree);
+ else
+ leaf->tree = NULL;
+ return 1;
+ }
+ if (!S_ISDIR(e->versions[1].mode))
+ return 0;
+ if (!e->tree)
+ load_tree(e);
+ return tree_content_get(e, slash1 + 1, leaf);
+ }
+ }
+ return 0;
+}
+
static int update_branch(struct branch *b)
{
static const char *msg = "fast-import";
static void read_next_command(void)
{
- read_line(&command_buf, stdin, '\n');
+ do {
+ if (unread_command_buf) {
+ unread_command_buf = 0;
+ if (command_buf.eof)
+ return;
+ } else {
+ struct recent_command *rc;
+
+ command_buf.buf = NULL;
+ read_line(&command_buf, stdin, '\n');
+ if (command_buf.eof)
+ return;
+
+ rc = rc_free;
+ if (rc)
+ rc_free = rc->next;
+ else {
+ rc = cmd_hist.next;
+ cmd_hist.next = rc->next;
+ cmd_hist.next->prev = &cmd_hist;
+ free(rc->buf);
+ }
+
+ rc->buf = command_buf.buf;
+ rc->prev = cmd_tail;
+ rc->next = cmd_hist.prev;
+ rc->prev->next = rc;
+ cmd_tail = rc;
+ }
+ } while (command_buf.buf[0] == '#');
+}
+
+static void skip_optional_lf(void)
+{
+ int term_char = fgetc(stdin);
+ if (term_char != '\n' && term_char != EOF)
+ ungetc(term_char, stdin);
}
static void cmd_mark(void)
size_t sz = 8192, term_len = command_buf.len - 5 - 2;
length = 0;
buffer = xmalloc(sz);
+ command_buf.buf = NULL;
for (;;) {
- read_next_command();
+ read_line(&command_buf, stdin, '\n');
if (command_buf.eof)
die("EOF in data (terminator '%s' not found)", term);
if (term_len == command_buf.len
&& !strcmp(term, command_buf.buf))
break;
- if (sz < (length + command_buf.len)) {
- sz = sz * 3 / 2 + 16;
- if (sz < (length + command_buf.len))
- sz = length + command_buf.len;
- buffer = xrealloc(buffer, sz);
- }
+ ALLOC_GROW(buffer, length + command_buf.len, sz);
memcpy(buffer + length,
command_buf.buf,
command_buf.len - 1);
}
}
- if (fgetc(stdin) != '\n')
- die("An lf did not trail the binary data as expected.");
-
+ skip_optional_lf();
*size = length;
return buffer;
}
} else if (oe) {
if (oe->type != OBJ_BLOB)
die("Not a blob (actually a %s): %s",
- command_buf.buf, typename(oe->type));
+ typename(oe->type), command_buf.buf);
} else {
enum object_type type = sha1_object_info(sha1, NULL);
if (type < 0)
typename(type), command_buf.buf);
}
- tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode);
+ tree_content_set(&b->branch_tree, p, sha1, S_IFREG | mode, NULL);
free(p_uq);
}
die("Garbage after path in: %s", command_buf.buf);
p = p_uq;
}
- tree_content_remove(&b->branch_tree, p);
+ tree_content_remove(&b->branch_tree, p, NULL);
free(p_uq);
}
+static void file_change_cr(struct branch *b, int rename)
+{
+ const char *s, *d;
+ char *s_uq, *d_uq;
+ const char *endp;
+ struct tree_entry leaf;
+
+ s = command_buf.buf + 2;
+ s_uq = unquote_c_style(s, &endp);
+ if (s_uq) {
+ if (*endp != ' ')
+ die("Missing space after source: %s", command_buf.buf);
+ }
+ else {
+ endp = strchr(s, ' ');
+ if (!endp)
+ die("Missing space after source: %s", command_buf.buf);
+ s_uq = xmalloc(endp - s + 1);
+ memcpy(s_uq, s, endp - s);
+ s_uq[endp - s] = 0;
+ }
+ s = s_uq;
+
+ endp++;
+ if (!*endp)
+ die("Missing dest: %s", command_buf.buf);
+
+ d = endp;
+ d_uq = unquote_c_style(d, &endp);
+ if (d_uq) {
+ if (*endp)
+ die("Garbage after dest in: %s", command_buf.buf);
+ d = d_uq;
+ }
+
+ memset(&leaf, 0, sizeof(leaf));
+ if (rename)
+ tree_content_remove(&b->branch_tree, s, &leaf);
+ else
+ tree_content_get(&b->branch_tree, s, &leaf);
+ if (!leaf.versions[1].mode)
+ die("Path %s not in branch", s);
+ tree_content_set(&b->branch_tree, d,
+ leaf.versions[1].sha1,
+ leaf.versions[1].mode,
+ leaf.tree);
+
+ free(s_uq);
+ free(d_uq);
+}
+
static void file_change_deleteall(struct branch *b)
{
release_tree_content_recursive(b->branch_tree.tree);
}
}
-static void cmd_from(struct branch *b)
+static int cmd_from(struct branch *b)
{
const char *from;
struct branch *s;
if (prefixcmp(command_buf.buf, "from "))
- return;
+ return 0;
if (b->branch_tree.tree) {
release_tree_content_recursive(b->branch_tree.tree);
die("Invalid ref name or SHA1 expression: %s", from);
read_next_command();
+ return 1;
}
static struct hash_list *cmd_merge(unsigned int *count)
}
/* file_change* */
- for (;;) {
- if (1 == command_buf.len)
- break;
- else if (!prefixcmp(command_buf.buf, "M "))
+ while (!command_buf.eof && command_buf.len > 1) {
+ if (!prefixcmp(command_buf.buf, "M "))
file_change_m(b);
else if (!prefixcmp(command_buf.buf, "D "))
file_change_d(b);
+ else if (!prefixcmp(command_buf.buf, "R "))
+ file_change_cr(b, 1);
+ else if (!prefixcmp(command_buf.buf, "C "))
+ file_change_cr(b, 0);
else if (!strcmp("deleteall", command_buf.buf))
file_change_deleteall(b);
- else
- die("Unsupported file_change: %s", command_buf.buf);
+ else {
+ unread_command_buf = 1;
+ break;
+ }
read_next_command();
}
else
b = new_branch(sp);
read_next_command();
- cmd_from(b);
+ if (!cmd_from(b) && command_buf.len > 1)
+ unread_command_buf = 1;
}
static void cmd_checkpoint(void)
dump_tags();
dump_marks();
}
- read_next_command();
+ skip_optional_lf();
+}
+
+static void cmd_progress(void)
+{
+ fwrite(command_buf.buf, 1, command_buf.len - 1, stdout);
+ fputc('\n', stdout);
+ fflush(stdout);
+ skip_optional_lf();
}
static void import_marks(const char *input_file)
int main(int argc, const char **argv)
{
- int i, show_stats = 1;
+ unsigned int i, show_stats = 1;
git_config(git_default_config);
alloc_objects(object_entry_alloc);
if (i != argc)
usage(fast_import_usage);
+ rc_free = pool_alloc(cmd_save * sizeof(*rc_free));
+ for (i = 0; i < (cmd_save - 1); i++)
+ rc_free[i].next = &rc_free[i + 1];
+ rc_free[cmd_save - 1].next = NULL;
+
prepare_packed_git();
start_packfile();
+ set_die_routine(die_nicely);
for (;;) {
read_next_command();
if (command_buf.eof)
cmd_reset_branch();
else if (!strcmp("checkpoint", command_buf.buf))
cmd_checkpoint();
+ else if (!prefixcmp(command_buf.buf, "progress "))
+ cmd_progress();
else
die("Unsupported command: %s", command_buf.buf);
}