diff options
-rw-r--r-- | .gitignore | 1 | ||||
-rw-r--r-- | Documentation/git-format-patch.txt | 14 | ||||
-rw-r--r-- | Makefile | 4 | ||||
-rw-r--r-- | blame.c | 239 | ||||
-rw-r--r-- | commit.c | 36 | ||||
-rw-r--r-- | commit.h | 20 | ||||
-rwxr-xr-x | git-annotate.perl | 2 | ||||
-rwxr-xr-x | git-diff.sh | 4 | ||||
-rwxr-xr-x | git-merge.sh | 2 | ||||
-rwxr-xr-x | git-repack.sh | 1 | ||||
-rwxr-xr-x | git-resolve.sh | 2 | ||||
-rw-r--r-- | http-push.c | 1051 | ||||
-rw-r--r-- | http.c | 8 | ||||
-rw-r--r-- | http.h | 1 | ||||
-rw-r--r-- | imap-send.c | 1359 | ||||
-rw-r--r-- | rev-list.c | 6 | ||||
-rw-r--r-- | revision.c | 118 | ||||
-rw-r--r-- | revision.h | 18 | ||||
-rw-r--r-- | t/annotate-tests.sh | 2 | ||||
-rwxr-xr-x | t/t1200-tutorial.sh | 2 |
20 files changed, 2512 insertions, 378 deletions
diff --git a/.gitignore b/.gitignore index 8e94cbde67..b4355b9faf 100644 --- a/.gitignore +++ b/.gitignore @@ -42,6 +42,7 @@ git-grep git-hash-object git-http-fetch git-http-push +git-imap-send git-index-pack git-init-db git-local-fetch diff --git a/Documentation/git-format-patch.txt b/Documentation/git-format-patch.txt index 7c467c56a3..7cc7fafc1d 100644 --- a/Documentation/git-format-patch.txt +++ b/Documentation/git-format-patch.txt @@ -9,7 +9,7 @@ git-format-patch - Prepare patches for e-mail submission SYNOPSIS -------- [verse] -'git-format-patch' [-n | -k] [-o <dir> | --stdout] [-s] [-c] +'git-format-patch' [-n | -k] [-o <dir> | --stdout] [--attach] [-s] [-c] [--diff-options] <his> [<mine>] DESCRIPTION @@ -60,6 +60,18 @@ OPTIONS standard output, instead of saving them into a file per patch and implies --mbox. +--attach:: + Create attachments instead of inlining patches. + + +CONFIGURATION +------------- +You can specify extra mail header lines to be added to each +message in the repository configuration as follows: + +[format] + headers = "Organization: git-foo\n" + EXAMPLES -------- @@ -165,7 +165,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ - git-describe$X git-merge-tree$X git-blame$X + git-describe$X git-merge-tree$X git-blame$X git-imap-send$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -525,6 +525,8 @@ git-ssh-upload$X: rsh.o git-ssh-pull$X: rsh.o fetch.o git-ssh-push$X: rsh.o +git-imap-send$X: imap-send.o $(LIB_FILE) + git-http-fetch$X: fetch.o http.o http-fetch.o $(LIB_FILE) $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \ $(LIBS) $(CURL_LIBCURL) @@ -14,6 +14,7 @@ #include "tree.h" #include "blob.h" #include "diff.h" +#include "diffcore.h" #include "revision.h" #define DEBUG 0 @@ -34,7 +35,9 @@ struct util_info { char *buf; unsigned long size; int num_lines; -// const char* path; + const char* pathname; + + void* topo_data; }; struct chunk { @@ -342,25 +345,34 @@ static int map_line(struct commit *commit, int line) return info->line_map[line]; } -static int fill_util_info(struct commit *commit, const char *path) +static struct util_info* get_util(struct commit *commit) { - struct util_info *util; - if (commit->object.util) - return 0; + struct util_info *util = commit->object.util; + + if (util) + return util; util = xmalloc(sizeof(struct util_info)); + util->buf = NULL; + util->size = 0; + util->line_map = NULL; + util->num_lines = -1; + util->pathname = NULL; + commit->object.util = util; + return util; +} + +static int fill_util_info(struct commit *commit) +{ + struct util_info *util = commit->object.util; + + assert(util); + assert(util->pathname); - if (get_blob_sha1(commit->tree, path, util->sha1)) { - free(util); + if (get_blob_sha1(commit->tree, util->pathname, util->sha1)) return 1; - } else { - util->buf = NULL; - util->size = 0; - util->line_map = NULL; - util->num_lines = -1; - commit->object.util = util; + else return 0; - } } static void alloc_line_map(struct commit *commit) @@ -389,10 +401,11 @@ static void alloc_line_map(struct commit *commit) static void init_first_commit(struct commit* commit, const char* filename) { - struct util_info* util; + struct util_info* util = commit->object.util; int i; - if (fill_util_info(commit, filename)) + util->pathname = filename; + if (fill_util_info(commit)) die("fill_util_info failed"); alloc_line_map(commit); @@ -453,7 +466,7 @@ static void process_commits(struct rev_info *rev, const char *path, if(num_parents == 0) *initial = commit; - if(fill_util_info(commit, path)) + if (fill_util_info(commit)) continue; alloc_line_map(commit); @@ -471,7 +484,7 @@ static void process_commits(struct rev_info *rev, const char *path, printf("parent: %s\n", sha1_to_hex(parent->object.sha1)); - if(fill_util_info(parent, path)) { + if (fill_util_info(parent)) { num_parents--; continue; } @@ -511,6 +524,135 @@ static void process_commits(struct rev_info *rev, const char *path, } while ((commit = get_revision(rev)) != NULL); } + +static int compare_tree_path(struct rev_info* revs, + struct commit* c1, struct commit* c2) +{ + const char* paths[2]; + struct util_info* util = c2->object.util; + paths[0] = util->pathname; + paths[1] = NULL; + + diff_tree_setup_paths(get_pathspec(revs->prefix, paths)); + return rev_compare_tree(c1->tree, c2->tree); +} + + +static int same_tree_as_empty_path(struct rev_info *revs, struct tree* t1, + const char* path) +{ + const char* paths[2]; + paths[0] = path; + paths[1] = NULL; + + diff_tree_setup_paths(get_pathspec(revs->prefix, paths)); + return rev_same_tree_as_empty(t1); +} + +static const char* find_rename(struct commit* commit, struct commit* parent) +{ + struct util_info* cutil = commit->object.util; + struct diff_options diff_opts; + const char *paths[1]; + int i; + + if (DEBUG) { + printf("find_rename commit: %s ", + sha1_to_hex(commit->object.sha1)); + puts(sha1_to_hex(parent->object.sha1)); + } + + diff_setup(&diff_opts); + diff_opts.recursive = 1; + diff_opts.detect_rename = DIFF_DETECT_RENAME; + paths[0] = NULL; + diff_tree_setup_paths(paths); + if (diff_setup_done(&diff_opts) < 0) + die("diff_setup_done failed"); + + diff_tree_sha1(commit->tree->object.sha1, parent->tree->object.sha1, + "", &diff_opts); + diffcore_std(&diff_opts); + + for (i = 0; i < diff_queued_diff.nr; i++) { + struct diff_filepair *p = diff_queued_diff.queue[i]; + + if (p->status == 'R' && !strcmp(p->one->path, cutil->pathname)) { + if (DEBUG) + printf("rename %s -> %s\n", p->one->path, p->two->path); + return p->two->path; + } + } + + return 0; +} + +static void simplify_commit(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp, *parent; + + if (!commit->tree) + return; + + if (!commit->parents) { + struct util_info* util = commit->object.util; + if (!same_tree_as_empty_path(revs, commit->tree, + util->pathname)) + commit->object.flags |= TREECHANGE; + return; + } + + pp = &commit->parents; + while ((parent = *pp) != NULL) { + struct commit *p = parent->item; + + if (p->object.flags & UNINTERESTING) { + pp = &parent->next; + continue; + } + + parse_commit(p); + switch (compare_tree_path(revs, p, commit)) { + case REV_TREE_SAME: + parent->next = NULL; + commit->parents = parent; + get_util(p)->pathname = get_util(commit)->pathname; + return; + + case REV_TREE_NEW: + { + + struct util_info* util = commit->object.util; + if (revs->remove_empty_trees && + same_tree_as_empty_path(revs, p->tree, + util->pathname)) { + const char* new_name = find_rename(commit, p); + if (new_name) { + struct util_info* putil = get_util(p); + if (!putil->pathname) + putil->pathname = strdup(new_name); + } else { + *pp = parent->next; + continue; + } + } + } + + /* fallthrough */ + case REV_TREE_DIFFERENT: + pp = &parent->next; + if (!get_util(p)->pathname) + get_util(p)->pathname = + get_util(commit)->pathname; + continue; + } + die("bad tree compare for commit %s", + sha1_to_hex(commit->object.sha1)); + } + commit->object.flags |= TREECHANGE; +} + + struct commit_info { char* author; @@ -569,6 +711,18 @@ static const char* format_time(unsigned long time, const char* tz_str) return time_buf; } +static void topo_setter(struct commit* c, void* data) +{ + struct util_info* util = c->object.util; + util->topo_data = data; +} + +static void* topo_getter(struct commit* c) +{ + struct util_info* util = c->object.util; + return util->topo_data; +} + int main(int argc, const char **argv) { int i; @@ -580,8 +734,8 @@ int main(int argc, const char **argv) int sha1_len = 8; int compability = 0; int options = 1; + struct commit* start_commit; - int num_args; const char* args[10]; struct rev_info rev; @@ -634,28 +788,29 @@ int main(int argc, const char **argv) strcpy(filename_buf, filename); filename = filename_buf; - { - struct commit* c; - if (get_sha1(commit, sha1)) - die("get_sha1 failed, commit '%s' not found", commit); - c = lookup_commit_reference(sha1); - - if (fill_util_info(c, filename)) { - printf("%s not found in %s\n", filename, commit); - return 1; - } + if (get_sha1(commit, sha1)) + die("get_sha1 failed, commit '%s' not found", commit); + start_commit = lookup_commit_reference(sha1); + get_util(start_commit)->pathname = filename; + if (fill_util_info(start_commit)) { + printf("%s not found in %s\n", filename, commit); + return 1; } - num_args = 0; - args[num_args++] = NULL; - args[num_args++] = "--topo-order"; - args[num_args++] = "--remove-empty"; - args[num_args++] = commit; - args[num_args++] = "--"; - args[num_args++] = filename; - args[num_args] = NULL; - setup_revisions(num_args, args, &rev, "HEAD"); + init_revisions(&rev); + rev.remove_empty_trees = 1; + rev.topo_order = 1; + rev.prune_fn = simplify_commit; + rev.topo_setter = topo_setter; + rev.topo_getter = topo_getter; + rev.limited = 1; + + commit_list_insert(start_commit, &rev.commits); + + args[0] = filename; + args[1] = NULL; + diff_tree_setup_paths(args); prepare_revision_walk(&rev); process_commits(&rev, filename, &initial); @@ -665,17 +820,21 @@ int main(int argc, const char **argv) for (i = 0; i < num_blame_lines; i++) { struct commit *c = blame_lines[i]; + struct util_info* u; + if (!c) c = initial; + u = c->object.util; get_commit_info(c, &ci); fwrite(sha1_to_hex(c->object.sha1), sha1_len, 1, stdout); if(compability) printf("\t(%10s\t%10s\t%d)", ci.author, format_time(ci.author_time, ci.author_tz), i+1); else - printf(" (%-15.15s %10s %*d) ", ci.author, - format_time(ci.author_time, ci.author_tz), + printf(" %s (%-15.15s %10s %*d) ", u->pathname, + ci.author, format_time(ci.author_time, + ci.author_tz), max_digits, i+1); if(i == num_blame_lines - 1) { @@ -569,11 +569,29 @@ int count_parents(struct commit * commit) return count; } +void topo_sort_default_setter(struct commit *c, void *data) +{ + c->object.util = data; +} + +void *topo_sort_default_getter(struct commit *c) +{ + return c->object.util; +} + /* * Performs an in-place topological sort on the list supplied. */ void sort_in_topological_order(struct commit_list ** list, int lifo) { + sort_in_topological_order_fn(list, lifo, topo_sort_default_setter, + topo_sort_default_getter); +} + +void sort_in_topological_order_fn(struct commit_list ** list, int lifo, + topo_sort_set_fn_t setter, + topo_sort_get_fn_t getter) +{ struct commit_list * next = *list; struct commit_list * work = NULL, **insert; struct commit_list ** pptr = list; @@ -596,7 +614,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) next=*list; while (next) { next_nodes->list_item = next; - next->item->object.util = next_nodes; + setter(next->item, next_nodes); next_nodes++; next = next->next; } @@ -606,8 +624,8 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) struct commit_list * parents = next->item->parents; while (parents) { struct commit * parent=parents->item; - struct sort_node * pn = (struct sort_node *)parent->object.util; - + struct sort_node * pn = (struct sort_node *) getter(parent); + if (pn) pn->indegree++; parents=parents->next; @@ -624,7 +642,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) next=*list; insert = &work; while (next) { - struct sort_node * node = (struct sort_node *)next->item->object.util; + struct sort_node * node = (struct sort_node *) getter(next->item); if (node->indegree == 0) { insert = &commit_list_insert(next->item, insert)->next; @@ -637,15 +655,15 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) sort_by_date(&work); while (work) { struct commit * work_item = pop_commit(&work); - struct sort_node * work_node = (struct sort_node *)work_item->object.util; + struct sort_node * work_node = (struct sort_node *) getter(work_item); struct commit_list * parents = work_item->parents; while (parents) { struct commit * parent=parents->item; - struct sort_node * pn = (struct sort_node *)parent->object.util; - + struct sort_node * pn = (struct sort_node *) getter(parent); + if (pn) { - /* + /* * parents are only enqueued for emission * when all their children have been emitted thereby * guaranteeing topological order. @@ -667,7 +685,7 @@ void sort_in_topological_order(struct commit_list ** list, int lifo) *pptr = work_node->list_item; pptr = &(*pptr)->next; *pptr = NULL; - work_item->object.util = NULL; + setter(work_item, NULL); } free(nodes); } @@ -65,15 +65,29 @@ int count_parents(struct commit * commit); /* * Performs an in-place topological sort of list supplied. * - * Pre-conditions: + * Pre-conditions for sort_in_topological_order: * all commits in input list and all parents of those * commits must have object.util == NULL - * - * Post-conditions: + * + * Pre-conditions for sort_in_topological_order_fn: + * all commits in input list and all parents of those + * commits must have getter(commit) == NULL + * + * Post-conditions: * invariant of resulting list is: * a reachable from b => ord(b) < ord(a) * in addition, when lifo == 0, commits on parallel tracks are * sorted in the dates order. */ + +typedef void (*topo_sort_set_fn_t)(struct commit*, void *data); +typedef void* (*topo_sort_get_fn_t)(struct commit*); + +void topo_sort_default_setter(struct commit *c, void *data); +void *topo_sort_default_getter(struct commit *c); + void sort_in_topological_order(struct commit_list ** list, int lifo); +void sort_in_topological_order_fn(struct commit_list ** list, int lifo, + topo_sort_set_fn_t setter, + topo_sort_get_fn_t getter); #endif /* COMMIT_H */ diff --git a/git-annotate.perl b/git-annotate.perl index feea0a2d81..9df72a1662 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -20,7 +20,7 @@ sub usage() { -r, --rename Follow renames (Defaults on). -S, --rev-file revs-file - use revs from revs-file instead of calling git-rev-list + Use revs from revs-file instead of calling git-rev-list -h, --help This message. '; diff --git a/git-diff.sh b/git-diff.sh index dc4d1b3cfd..dc0dd312bf 100755 --- a/git-diff.sh +++ b/git-diff.sh @@ -38,9 +38,9 @@ case " $flags " in flags="$flags'$cc_or_p' " ;; esac -# If we do not have -B nor -C, default to -M. +# If we do not have -B, -C, -r, nor -p, default to -M. case " $flags " in -*" '-"[BCM]* | *" '--find-copies-harder' "*) +*" '-"[BCMrp]* | *" '--find-copies-harder' "*) ;; # something like -M50. *) flags="$flags'-M' " ;; diff --git a/git-merge.sh b/git-merge.sh index 7be9e81f1f..cc0952a97d 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -131,7 +131,7 @@ case "$#,$common,$no_commit" in ;; 1,"$head",*) # Again the most common case of merging one remote. - echo "Updating from $head to $1." + echo "Updating from $head to $1" git-update-index --refresh 2>/dev/null new_head=$(git-rev-parse --verify "$1^0") && git-read-tree -u -v -m $head "$new_head" && diff --git a/git-repack.sh b/git-repack.sh index 3d6fec1c9a..bc901126bf 100755 --- a/git-repack.sh +++ b/git-repack.sh @@ -75,6 +75,7 @@ then done ) fi + git-prune-packed fi case "$no_update_info" in diff --git a/git-resolve.sh b/git-resolve.sh index b53ede8d87..1c7aaefa25 100755 --- a/git-resolve.sh +++ b/git-resolve.sh @@ -41,7 +41,7 @@ case "$common" in exit 0 ;; "$head") - echo "Updating from $head to $merge." + echo "Updating from $head to $merge" git-read-tree -u -m $head $merge || exit 1 git-update-ref HEAD "$merge" "$head" git-diff-tree -p $head $merge | git-apply --stat diff --git a/http-push.c b/http-push.c index 226d71966d..42b0d59e8c 100644 --- a/http-push.c +++ b/http-push.c @@ -11,7 +11,7 @@ #include <expat.h> static const char http_push_usage[] = -"git-http-push [--complete] [--force] [--verbose] <url> <ref> [<ref>...]\n"; +"git-http-push [--all] [--force] [--verbose] <remote> [<head>...]\n"; #ifndef XML_STATUS_OK enum XML_Status { @@ -22,6 +22,7 @@ enum XML_Status { #define XML_STATUS_ERROR 0 #endif +#define PREV_BUF_SIZE 4096 #define RANGE_HEADER_SIZE 30 /* DAV methods */ @@ -58,9 +59,10 @@ enum XML_Status { /* bits #0-4 in revision.h */ -#define LOCAL (1u << 5) -#define REMOTE (1u << 6) -#define PUSHING (1u << 7) +#define LOCAL (1u << 5) +#define REMOTE (1u << 6) +#define FETCHING (1u << 7) +#define PUSHING (1u << 8) static int pushing = 0; static int aborted = 0; @@ -79,13 +81,19 @@ struct repo { char *url; int path_len; + int has_info_refs; + int can_update_info_refs; + int has_info_packs; struct packed_git *packs; + struct remote_lock *locks; }; static struct repo *remote = NULL; -static struct remote_lock *remote_locks = NULL; enum transfer_state { + NEED_FETCH, + RUN_FETCH_LOOSE, + RUN_FETCH_PACKED, NEED_PUSH, RUN_MKCOL, RUN_PUT, @@ -104,6 +112,8 @@ struct transfer_request struct buffer buffer; char filename[PATH_MAX]; char tmpfile[PATH_MAX]; + int local_fileno; + FILE *local_stream; enum transfer_state state; CURLcode curl_result; char errorstr[CURL_ERROR_SIZE]; @@ -113,6 +123,7 @@ struct transfer_request z_stream stream; int zret; int rename; + void *userData; struct active_request_slot *slot; struct transfer_request *next; }; @@ -135,19 +146,31 @@ struct remote_lock char *token; time_t start_time; long timeout; - int active; int refreshing; struct remote_lock *next; }; -struct remote_dentry +/* Flags that control remote_ls processing */ +#define PROCESS_FILES (1u << 0) +#define PROCESS_DIRS (1u << 1) +#define RECURSIVE (1u << 2) + +/* Flags that remote_ls passes to callback functions */ +#define IS_DIR (1u << 0) + +struct remote_ls_ctx { - char *base; - char *name; - int is_dir; + char *path; + void (*userFunc)(struct remote_ls_ctx *ls); + void *userData; + int flags; + char *dentry_name; + int dentry_flags; + struct remote_ls_ctx *parent; }; static void finish_request(struct transfer_request *request); +static void release_request(struct transfer_request *request); static void process_response(void *callback_data) { @@ -157,6 +180,258 @@ static void process_response(void *callback_data) finish_request(request); } +static size_t fwrite_sha1_file(void *ptr, size_t eltsize, size_t nmemb, + void *data) +{ + unsigned char expn[4096]; + size_t size = eltsize * nmemb; + int posn = 0; + struct transfer_request *request = (struct transfer_request *)data; + do { + ssize_t retval = write(request->local_fileno, + ptr + posn, size - posn); + if (retval < 0) + return posn; + posn += retval; + } while (posn < size); + + request->stream.avail_in = size; + request->stream.next_in = ptr; + do { + request->stream.next_out = expn; + request->stream.avail_out = sizeof(expn); + request->zret = inflate(&request->stream, Z_SYNC_FLUSH); + SHA1_Update(&request->c, expn, + sizeof(expn) - request->stream.avail_out); + } while (request->stream.avail_in && request->zret == Z_OK); + data_received++; + return size; +} + +static void start_fetch_loose(struct transfer_request *request) +{ + char *hex = sha1_to_hex(request->obj->sha1); + char *filename; + char prevfile[PATH_MAX]; + char *url; + char *posn; + int prevlocal; + unsigned char prev_buf[PREV_BUF_SIZE]; + ssize_t prev_read = 0; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + struct active_request_slot *slot; + + filename = sha1_file_name(request->obj->sha1); + snprintf(request->filename, sizeof(request->filename), "%s", filename); + snprintf(request->tmpfile, sizeof(request->tmpfile), + "%s.temp", filename); + + snprintf(prevfile, sizeof(prevfile), "%s.prev", request->filename); + unlink(prevfile); + rename(request->tmpfile, prevfile); + unlink(request->tmpfile); + + if (request->local_fileno != -1) + error("fd leakage in start: %d", request->local_fileno); + request->local_fileno = open(request->tmpfile, + O_WRONLY | O_CREAT | O_EXCL, 0666); + /* This could have failed due to the "lazy directory creation"; + * try to mkdir the last path component. + */ + if (request->local_fileno < 0 && errno == ENOENT) { + char *dir = strrchr(request->tmpfile, '/'); + if (dir) { + *dir = 0; + mkdir(request->tmpfile, 0777); + *dir = '/'; + } + request->local_fileno = open(request->tmpfile, + O_WRONLY | O_CREAT | O_EXCL, 0666); + } + + if (request->local_fileno < 0) { + request->state = ABORTED; + error("Couldn't create temporary file %s for %s: %s", + request->tmpfile, request->filename, strerror(errno)); + return; + } + + memset(&request->stream, 0, sizeof(request->stream)); + + inflateInit(&request->stream); + + SHA1_Init(&request->c); + + url = xmalloc(strlen(remote->url) + 50); + request->url = xmalloc(strlen(remote->url) + 50); + strcpy(url, remote->url); + posn = url + strlen(remote->url); + strcpy(posn, "objects/"); + posn += 8; + memcpy(posn, hex, 2); + posn += 2; + *(posn++) = '/'; + strcpy(posn, hex + 2); + strcpy(request->url, url); + + /* If a previous temp file is present, process what was already + fetched. */ + prevlocal = open(prevfile, O_RDONLY); + if (prevlocal != -1) { + do { + prev_read = read(prevlocal, prev_buf, PREV_BUF_SIZE); + if (prev_read>0) { + if (fwrite_sha1_file(prev_buf, + 1, + prev_read, + request) == prev_read) { + prev_posn += prev_read; + } else { + prev_read = -1; + } + } + } while (prev_read > 0); + close(prevlocal); + } + unlink(prevfile); + + /* Reset inflate/SHA1 if there was an error reading the previous temp + file; also rewind to the beginning of the local file. */ + if (prev_read == -1) { + memset(&request->stream, 0, sizeof(request->stream)); + inflateInit(&request->stream); + SHA1_Init(&request->c); + if (prev_posn>0) { + prev_posn = 0; + lseek(request->local_fileno, SEEK_SET, 0); + ftruncate(request->local_fileno, 0); + } + } + + slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; + request->slot = slot; + + curl_easy_setopt(slot->curl, CURLOPT_FILE, request); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_sha1_file); + curl_easy_setopt(slot->curl, CURLOPT_ERRORBUFFER, request->errorstr); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + + /* If we have successfully processed data from a previous fetch + attempt, only fetch the data we don't already have. */ + if (prev_posn>0) { + if (push_verbosely) + fprintf(stderr, + "Resuming fetch of object %s at byte %ld\n", + hex, prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(slot->curl, + CURLOPT_HTTPHEADER, range_header); + } + + /* Try to get the request started, abort the request on error */ + request->state = RUN_FETCH_LOOSE; + if (!start_active_slot(slot)) { + fprintf(stderr, "Unable to start GET request\n"); + remote->can_update_info_refs = 0; + release_request(request); + } +} + +static void start_fetch_packed(struct transfer_request *request) +{ + char *url; + struct packed_git *target; + FILE *packfile; + char *filename; + long prev_posn = 0; + char range[RANGE_HEADER_SIZE]; + struct curl_slist *range_header = NULL; + + struct transfer_request *check_request = request_queue_head; + struct active_request_slot *slot; + + target = find_sha1_pack(request->obj->sha1, remote->packs); + if (!target) { + fprintf(stderr, "Unable to fetch %s, will not be able to update server info refs\n", sha1_to_hex(request->obj->sha1)); + remote->can_update_info_refs = 0; + release_request(request); + return; + } + + fprintf(stderr, "Fetching pack %s\n", sha1_to_hex(target->sha1)); + fprintf(stderr, " which contains %s\n", sha1_to_hex(request->obj->sha1)); + + filename = sha1_pack_name(target->sha1); + snprintf(request->filename, sizeof(request->filename), "%s", filename); + snprintf(request->tmpfile, sizeof(request->tmpfile), + "%s.temp", filename); + + url = xmalloc(strlen(remote->url) + 64); + sprintf(url, "%sobjects/pack/pack-%s.pack", + remote->url, sha1_to_hex(target->sha1)); + + /* Make sure there isn't another open request for this pack */ + while (check_request) { + if (check_request->state == RUN_FETCH_PACKED && + !strcmp(check_request->url, url)) { + free(url); + release_request(request); + return; + } + check_request = check_request->next; + } + + packfile = fopen(request->tmpfile, "a"); + if (!packfile) { + fprintf(stderr, "Unable to open local file %s for pack", + filename); + remote->can_update_info_refs = 0; + free(url); + return; + } + + slot = get_active_slot(); + slot->callback_func = process_response; + slot->callback_data = request; + request->slot = slot; + request->local_stream = packfile; + request->userData = target; + + request->url = url; + curl_easy_setopt(slot->curl, CURLOPT_FILE, packfile); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite); + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, no_pragma_header); + slot->local = packfile; + + /* If there is data present from a previous transfer attempt, + resume where it left off */ + prev_posn = ftell(packfile); + if (prev_posn>0) { + if (push_verbosely) + fprintf(stderr, + "Resuming fetch of pack %s at byte %ld\n", + sha1_to_hex(target->sha1), prev_posn); + sprintf(range, "Range: bytes=%ld-", prev_posn); + range_header = curl_slist_append(range_header, range); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, range_header); + } + + /* Try to get the request started, abort the request on error */ + request->state = RUN_FETCH_PACKED; + if (!start_active_slot(slot)) { + fprintf(stderr, "Unable to start GET request\n"); + remote->can_update_info_refs = 0; + release_request(request); + } +} + static void start_mkcol(struct transfer_request *request) { char *hex = sha1_to_hex(request->obj->sha1); @@ -299,62 +574,69 @@ static void start_move(struct transfer_request *request) } } -static int refresh_lock(struct remote_lock *check_lock) +static int refresh_lock(struct remote_lock *lock) { struct active_request_slot *slot; + struct slot_results results; char *if_header; char timeout_header[25]; struct curl_slist *dav_headers = NULL; - struct remote_lock *lock; - int time_remaining; - time_t current_time; + int rc = 0; - /* Refresh all active locks if they're close to expiring */ - for (lock = remote_locks; lock; lock = lock->next) { - if (!lock->active) - continue; + lock->refreshing = 1; - current_time = time(NULL); - time_remaining = lock->start_time + lock->timeout - - current_time; - if (time_remaining > LOCK_REFRESH) - continue; + if_header = xmalloc(strlen(lock->token) + 25); + sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token); + sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout); + dav_headers = curl_slist_append(dav_headers, if_header); + dav_headers = curl_slist_append(dav_headers, timeout_header); - lock->refreshing = 1; + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); - if_header = xmalloc(strlen(lock->token) + 25); - sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token); - sprintf(timeout_header, "Timeout: Second-%ld", lock->timeout); - dav_headers = curl_slist_append(dav_headers, if_header); - dav_headers = curl_slist_append(dav_headers, timeout_header); + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.curl_result != CURLE_OK) { + fprintf(stderr, "LOCK HTTP error %ld\n", + results.http_code); + } else { + lock->start_time = time(NULL); + rc = 1; + } + } - slot = get_active_slot(); - curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); - curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_LOCK); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + lock->refreshing = 0; + curl_slist_free_all(dav_headers); + free(if_header); - if (start_active_slot(slot)) { - run_active_slot(slot); - if (slot->curl_result != CURLE_OK) { - fprintf(stderr, "Got HTTP error %ld\n", slot->http_code); - lock->active = 0; - } else { - lock->active = 1; - lock->start_time = time(NULL); + return rc; +} + +static void check_locks() +{ + struct remote_lock *lock = remote->locks; + time_t current_time = time(NULL); + int time_remaining; + + while (lock) { + time_remaining = lock->start_time + lock->timeout - + current_time; + if (!lock->refreshing && time_remaining < LOCK_REFRESH) { + if (!refresh_lock(lock)) { + fprintf(stderr, + "Unable to refresh lock for %s\n", + lock->url); + aborted = 1; + return; } } - - lock->refreshing = 0; - curl_slist_free_all(dav_headers); - free(if_header); + lock = lock->next; } - - if (check_lock) - return check_lock->active; - else - return 0; } static void release_request(struct transfer_request *request) @@ -370,6 +652,10 @@ static void release_request(struct transfer_request *request) entry->next = entry->next->next; } + if (request->local_fileno != -1) + close(request->local_fileno); + if (request->local_stream) + fclose(request->local_stream); if (request->url != NULL) free(request->url); free(request); @@ -377,12 +663,16 @@ static void release_request(struct transfer_request *request) static void finish_request(struct transfer_request *request) { - request->curl_result = request->slot->curl_result; + struct stat st; + struct packed_git *target; + struct packed_git **lst; + + request->curl_result = request->slot->curl_result; request->http_code = request->slot->http_code; request->slot = NULL; /* Keep locks active */ - refresh_lock(request->lock); + check_locks(); if (request->headers != NULL) curl_slist_free_all(request->headers); @@ -417,9 +707,9 @@ static void finish_request(struct transfer_request *request) } } else if (request->state == RUN_MOVE) { if (request->curl_result == CURLE_OK) { - fprintf(stderr, " sent %s\n", - sha1_to_hex(request->obj->sha1)); - request->state = COMPLETE; + if (push_verbosely) + fprintf(stderr, " sent %s\n", + sha1_to_hex(request->obj->sha1)); request->obj->flags |= REMOTE; release_request(request); } else { @@ -429,12 +719,73 @@ static void finish_request(struct transfer_request *request) request->state = ABORTED; aborted = 1; } + } else if (request->state == RUN_FETCH_LOOSE) { + fchmod(request->local_fileno, 0444); + close(request->local_fileno); request->local_fileno = -1; + + if (request->curl_result != CURLE_OK && + request->http_code != 416) { + if (stat(request->tmpfile, &st) == 0) { + if (st.st_size == 0) + unlink(request->tmpfile); + } + } else { + if (request->http_code == 416) + fprintf(stderr, "Warning: requested range invalid; we may already have all the data.\n"); + + inflateEnd(&request->stream); + SHA1_Final(request->real_sha1, &request->c); + if (request->zret != Z_STREAM_END) { + unlink(request->tmpfile); + } else if (memcmp(request->obj->sha1, request->real_sha1, 20)) { + unlink(request->tmpfile); + } else { + request->rename = + move_temp_to_file( + request->tmpfile, + request->filename); + if (request->rename == 0) { + request->obj->flags |= (LOCAL | REMOTE); + } + } + } + + /* Try fetching packed if necessary */ + if (request->obj->flags & LOCAL) + release_request(request); + else + start_fetch_packed(request); + + } else if (request->state == RUN_FETCH_PACKED) { + if (request->curl_result != CURLE_OK) { + fprintf(stderr, "Unable to get pack file %s\n%s", + request->url, curl_errorstr); + remote->can_update_info_refs = 0; + } else { + fclose(request->local_stream); + request->local_stream = NULL; + if (!move_temp_to_file(request->tmpfile, + request->filename)) { + target = (struct packed_git *)request->userData; + lst = &remote->packs; + while (*lst != target) + lst = &((*lst)->next); + *lst = (*lst)->next; + + if (!verify_pack(target, 0)) + install_packed_git(target); + else + remote->can_update_info_refs = 0; + } + } + release_request(request); } } void fill_active_slots(void) { struct transfer_request *request = request_queue_head; + struct transfer_request *next; struct active_request_slot *slot = active_queue_head; int num_transfers; @@ -442,7 +793,10 @@ void fill_active_slots(void) return; while (active_requests < max_requests && request != NULL) { - if (pushing && request->state == NEED_PUSH) { + next = request->next; + if (request->state == NEED_FETCH) { + start_fetch_loose(request); + } else if (pushing && request->state == NEED_PUSH) { if (remote_dir_exists[request->obj->sha1[0]] == 1) { start_put(request); } else { @@ -450,7 +804,7 @@ void fill_active_slots(void) } curl_multi_perform(curlm, &num_transfers); } - request = request->next; + request = next; } while (slot != NULL) { @@ -464,11 +818,45 @@ void fill_active_slots(void) static void get_remote_object_list(unsigned char parent); -static void add_request(struct object *obj, struct remote_lock *lock) +static void add_fetch_request(struct object *obj) +{ + struct transfer_request *request; + + check_locks(); + + /* + * Don't fetch the object if it's known to exist locally + * or is already in the request queue + */ + if (remote_dir_exists[obj->sha1[0]] == -1) + get_remote_object_list(obj->sha1[0]); + if (obj->flags & (LOCAL | FETCHING)) + return; + + obj->flags |= FETCHING; + request = xmalloc(sizeof(*request)); + request->obj = obj; + request->url = NULL; + request->lock = NULL; + request->headers = NULL; + request->local_fileno = -1; + request->local_stream = NULL; + request->state = NEED_FETCH; + request->next = request_queue_head; + request_queue_head = request; + + fill_active_slots(); + step_active_slots(); +} + +static int add_send_request(struct object *obj, struct remote_lock *lock) { struct transfer_request *request = request_queue_head; struct packed_git *target; + /* Keep locks active */ + check_locks(); + /* * Don't push the object if it's known to exist on the remote * or is already in the request queue @@ -476,11 +864,11 @@ static void add_request(struct object *obj, struct remote_lock *lock) if (remote_dir_exists[obj->sha1[0]] == -1) get_remote_object_list(obj->sha1[0]); if (obj->flags & (REMOTE | PUSHING)) - return; + return 0; target = find_sha1_pack(obj->sha1, remote->packs); if (target) { obj->flags |= REMOTE; - return; + return 0; } obj->flags |= PUSHING; @@ -489,12 +877,16 @@ static void add_request(struct object *obj, struct remote_lock *lock) request->url = NULL; request->lock = lock; request->headers = NULL; + request->local_fileno = -1; + request->local_stream = NULL; request->state = NEED_PUSH; request->next = request_queue_head; request_queue_head = request; fill_active_slots(); step_active_slots(); + + return 1; } static int fetch_index(unsigned char *sha1) @@ -509,16 +901,18 @@ static int fetch_index(unsigned char *sha1) FILE *indexfile; struct active_request_slot *slot; + struct slot_results results; /* Don't use the index if the pack isn't there */ - url = xmalloc(strlen(remote->url) + 65); - sprintf(url, "%s/objects/pack/pack-%s.pack", remote->url, hex); + url = xmalloc(strlen(remote->url) + 64); + sprintf(url, "%sobjects/pack/pack-%s.pack", remote->url, hex); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result != CURLE_OK) { + if (results.curl_result != CURLE_OK) { free(url); return error("Unable to verify pack %s is available", hex); @@ -532,9 +926,9 @@ static int fetch_index(unsigned char *sha1) if (push_verbosely) fprintf(stderr, "Getting index for pack %s\n", hex); - - sprintf(url, "%s/objects/pack/pack-%s.idx", remote->url, hex); - + + sprintf(url, "%sobjects/pack/pack-%s.idx", remote->url, hex); + filename = sha1_pack_index_name(sha1); snprintf(tmpfile, sizeof(tmpfile), "%s.temp", filename); indexfile = fopen(tmpfile, "a"); @@ -543,6 +937,7 @@ static int fetch_index(unsigned char *sha1) filename); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 0); curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); curl_easy_setopt(slot->curl, CURLOPT_FILE, indexfile); @@ -566,7 +961,7 @@ static int fetch_index(unsigned char *sha1) if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result != CURLE_OK) { + if (results.curl_result != CURLE_OK) { free(url); fclose(indexfile); return error("Unable to get pack index %s\n%s", url, @@ -606,6 +1001,7 @@ static int fetch_indices(void) int i = 0; struct active_request_slot *slot; + struct slot_results results; data = xmalloc(4096); memset(data, 0, 4096); @@ -615,21 +1011,22 @@ static int fetch_indices(void) if (push_verbosely) fprintf(stderr, "Getting pack list\n"); - - url = xmalloc(strlen(remote->url) + 21); - sprintf(url, "%s/objects/info/packs", remote->url); + + url = xmalloc(strlen(remote->url) + 20); + sprintf(url, "%sobjects/info/packs", remote->url); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result != CURLE_OK) { + if (results.curl_result != CURLE_OK) { free(buffer.buffer); free(url); - if (slot->http_code == 404) + if (results.http_code == 404) return 0; else return error("%s", curl_errorstr); @@ -716,20 +1113,22 @@ int fetch_ref(char *ref, unsigned char *sha1) struct buffer buffer; char *base = remote->url; struct active_request_slot *slot; + struct slot_results results; buffer.size = 41; buffer.posn = 0; buffer.buffer = hex; hex[41] = '\0'; - + url = quote_ref_url(base, ref); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_FILE, &buffer); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, NULL); curl_easy_setopt(slot->curl, CURLOPT_URL, url); if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result != CURLE_OK) + if (results.curl_result != CURLE_OK) return error("Couldn't get %s for %s\n%s", url, ref, curl_errorstr); } else { @@ -803,55 +1202,6 @@ static void handle_new_lock_ctx(struct xml_ctx *ctx, int tag_closed) } static void one_remote_ref(char *refname); -static void crawl_remote_refs(char *path); - -static void handle_crawl_ref_ctx(struct xml_ctx *ctx, int tag_closed) -{ - struct remote_dentry *dentry = (struct remote_dentry *)ctx->userData; - - - if (tag_closed) { - if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && dentry->name) { - if (dentry->is_dir) { - if (strcmp(dentry->name, dentry->base)) { - crawl_remote_refs(dentry->name); - } - } else { - one_remote_ref(dentry->name); - } - } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) { - dentry->name = xmalloc(strlen(ctx->cdata) - - remote->path_len + 1); - strcpy(dentry->name, - ctx->cdata + remote->path_len); - } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { - dentry->is_dir = 1; - } - } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { - dentry->name = NULL; - dentry->is_dir = 0; - } -} - -static void handle_remote_object_list_ctx(struct xml_ctx *ctx, int tag_closed) -{ - char *path; - char *obj_hex; - - if (tag_closed) { - if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) { - path = ctx->cdata + remote->path_len; - if (strlen(path) != 50) - return; - path += 9; - obj_hex = xmalloc(strlen(path)); - strncpy(obj_hex, path, 2); - strcpy(obj_hex + 2, path + 3); - one_remote_object(obj_hex); - free(obj_hex); - } - } -} static void xml_start_tag(void *userData, const char *name, const char **atts) @@ -913,6 +1263,7 @@ xml_cdata(void *userData, const XML_Char *s, int len) static struct remote_lock *lock_remote(char *path, long timeout) { struct active_request_slot *slot; + struct slot_results results; struct buffer out_buffer; struct buffer in_buffer; char *out_data; @@ -920,7 +1271,7 @@ static struct remote_lock *lock_remote(char *path, long timeout) char *url; char *ep; char timeout_header[25]; - struct remote_lock *lock = remote_locks; + struct remote_lock *lock = NULL; XML_Parser parser = XML_ParserCreate(NULL); enum XML_Status result; struct curl_slist *dav_headers = NULL; @@ -929,31 +1280,20 @@ static struct remote_lock *lock_remote(char *path, long timeout) url = xmalloc(strlen(remote->url) + strlen(path) + 1); sprintf(url, "%s%s", remote->url, path); - /* Make sure the url is not already locked */ - while (lock && strcmp(lock->url, url)) { - lock = lock->next; - } - if (lock) { - free(url); - if (refresh_lock(lock)) - return lock; - else - return NULL; - } - /* Make sure leading directories exist for the remote ref */ ep = strchr(url + strlen(remote->url) + 11, '/'); while (ep) { *ep = 0; slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_HTTPGET, 1); curl_easy_setopt(slot->curl, CURLOPT_URL, url); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_MKCOL); curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result != CURLE_OK && - slot->http_code != 405) { + if (results.curl_result != CURLE_OK && + results.http_code != 405) { fprintf(stderr, "Unable to create branch path %s\n", url); @@ -961,7 +1301,7 @@ static struct remote_lock *lock_remote(char *path, long timeout) return NULL; } } else { - fprintf(stderr, "Unable to start request\n"); + fprintf(stderr, "Unable to start MKCOL request\n"); free(url); return NULL; } @@ -985,6 +1325,7 @@ static struct remote_lock *lock_remote(char *path, long timeout) dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); @@ -996,14 +1337,11 @@ static struct remote_lock *lock_remote(char *path, long timeout) curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); lock = xcalloc(1, sizeof(*lock)); - lock->owner = NULL; - lock->token = NULL; lock->timeout = -1; - lock->refreshing = 0; if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result == CURLE_OK) { + if (results.curl_result == CURLE_OK) { ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1024,7 +1362,7 @@ static struct remote_lock *lock_remote(char *path, long timeout) } } } else { - fprintf(stderr, "Unable to start request\n"); + fprintf(stderr, "Unable to start LOCK request\n"); } curl_slist_free_all(dav_headers); @@ -1041,10 +1379,9 @@ static struct remote_lock *lock_remote(char *path, long timeout) lock = NULL; } else { lock->url = url; - lock->active = 1; lock->start_time = time(NULL); - lock->next = remote_locks; - remote_locks = lock; + lock->next = remote->locks; + remote->locks = lock; } return lock; @@ -1053,6 +1390,8 @@ static struct remote_lock *lock_remote(char *path, long timeout) static int unlock_remote(struct remote_lock *lock) { struct active_request_slot *slot; + struct slot_results results; + struct remote_lock *prev = remote->locks; char *lock_token_header; struct curl_slist *dav_headers = NULL; int rc = 0; @@ -1063,6 +1402,7 @@ static int unlock_remote(struct remote_lock *lock) dav_headers = curl_slist_append(dav_headers, lock_token_header); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_UNLOCK); @@ -1070,107 +1410,115 @@ static int unlock_remote(struct remote_lock *lock) if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result == CURLE_OK) + if (results.curl_result == CURLE_OK) rc = 1; else - fprintf(stderr, "Got HTTP error %ld\n", - slot->http_code); + fprintf(stderr, "UNLOCK HTTP error %ld\n", + results.http_code); } else { - fprintf(stderr, "Unable to start request\n"); + fprintf(stderr, "Unable to start UNLOCK request\n"); } curl_slist_free_all(dav_headers); free(lock_token_header); - lock->active = 0; + if (remote->locks == lock) { + remote->locks = lock->next; + } else { + while (prev && prev->next != lock) + prev = prev->next; + if (prev) + prev->next = prev->next->next; + } + + if (lock->owner != NULL) + free(lock->owner); + free(lock->url); + free(lock->token); + free(lock); return rc; } -static void crawl_remote_refs(char *path) -{ - char *url; - struct active_request_slot *slot; - struct buffer in_buffer; - struct buffer out_buffer; - char *in_data; - char *out_data; - XML_Parser parser = XML_ParserCreate(NULL); - enum XML_Status result; - struct curl_slist *dav_headers = NULL; - struct xml_ctx ctx; - struct remote_dentry dentry; - - fprintf(stderr, " %s\n", path); - - dentry.base = path; - dentry.name = NULL; - dentry.is_dir = 0; +static void remote_ls(const char *path, int flags, + void (*userFunc)(struct remote_ls_ctx *ls), + void *userData); - url = xmalloc(strlen(remote->url) + strlen(path) + 1); - sprintf(url, "%s%s", remote->url, path); +static void process_ls_object(struct remote_ls_ctx *ls) +{ + unsigned int *parent = (unsigned int *)ls->userData; + char *path = ls->dentry_name; + char *obj_hex; - out_buffer.size = strlen(PROPFIND_ALL_REQUEST); - out_data = xmalloc(out_buffer.size + 1); - snprintf(out_data, out_buffer.size + 1, PROPFIND_ALL_REQUEST); - out_buffer.posn = 0; - out_buffer.buffer = out_data; + if (!strcmp(ls->path, ls->dentry_name) && (ls->flags & IS_DIR)) { + remote_dir_exists[*parent] = 1; + return; + } - in_buffer.size = 4096; - in_data = xmalloc(in_buffer.size); - in_buffer.posn = 0; - in_buffer.buffer = in_data; + if (strlen(path) != 49) + return; + path += 8; + obj_hex = xmalloc(strlen(path)); + strncpy(obj_hex, path, 2); + strcpy(obj_hex + 2, path + 3); + one_remote_object(obj_hex); + free(obj_hex); +} - dav_headers = curl_slist_append(dav_headers, "Depth: 1"); - dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); +static void process_ls_ref(struct remote_ls_ctx *ls) +{ + if (!strcmp(ls->path, ls->dentry_name) && (ls->dentry_flags & IS_DIR)) { + fprintf(stderr, " %s\n", ls->dentry_name); + return; + } - slot = get_active_slot(); - curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); - curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); - curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); - curl_easy_setopt(slot->curl, CURLOPT_FILE, &in_buffer); - curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_buffer); - curl_easy_setopt(slot->curl, CURLOPT_URL, url); - curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); - curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PROPFIND); - curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + if (!(ls->dentry_flags & IS_DIR)) + one_remote_ref(ls->dentry_name); +} - if (start_active_slot(slot)) { - run_active_slot(slot); - if (slot->curl_result == CURLE_OK) { - ctx.name = xcalloc(10, 1); - ctx.len = 0; - ctx.cdata = NULL; - ctx.userFunc = handle_crawl_ref_ctx; - ctx.userData = &dentry; - XML_SetUserData(parser, &ctx); - XML_SetElementHandler(parser, xml_start_tag, - xml_end_tag); - XML_SetCharacterDataHandler(parser, xml_cdata); - result = XML_Parse(parser, in_buffer.buffer, - in_buffer.posn, 1); - free(ctx.name); +static void handle_remote_ls_ctx(struct xml_ctx *ctx, int tag_closed) +{ + struct remote_ls_ctx *ls = (struct remote_ls_ctx *)ctx->userData; - if (result != XML_STATUS_OK) { - fprintf(stderr, "XML error: %s\n", - XML_ErrorString( - XML_GetErrorCode(parser))); + if (tag_closed) { + if (!strcmp(ctx->name, DAV_PROPFIND_RESP) && ls->dentry_name) { + if (ls->dentry_flags & IS_DIR) { + if (ls->flags & PROCESS_DIRS) { + ls->userFunc(ls); + } + if (strcmp(ls->dentry_name, ls->path) && + ls->flags & RECURSIVE) { + remote_ls(ls->dentry_name, + ls->flags, + ls->userFunc, + ls->userData); + } + } else if (ls->flags & PROCESS_FILES) { + ls->userFunc(ls); } + } else if (!strcmp(ctx->name, DAV_PROPFIND_NAME) && ctx->cdata) { + ls->dentry_name = xmalloc(strlen(ctx->cdata) - + remote->path_len + 1); + strcpy(ls->dentry_name, ctx->cdata + remote->path_len); + } else if (!strcmp(ctx->name, DAV_PROPFIND_COLLECTION)) { + ls->dentry_flags |= IS_DIR; } - } else { - fprintf(stderr, "Unable to start request\n"); + } else if (!strcmp(ctx->name, DAV_PROPFIND_RESP)) { + if (ls->dentry_name) { + free(ls->dentry_name); + } + ls->dentry_name = NULL; + ls->dentry_flags = 0; } - - free(url); - free(out_data); - free(in_buffer.buffer); - curl_slist_free_all(dav_headers); } -static void get_remote_object_list(unsigned char parent) +static void remote_ls(const char *path, int flags, + void (*userFunc)(struct remote_ls_ctx *ls), + void *userData) { - char *url; + char *url = xmalloc(strlen(remote->url) + strlen(path) + 1); struct active_request_slot *slot; + struct slot_results results; struct buffer in_buffer; struct buffer out_buffer; char *in_data; @@ -1179,13 +1527,15 @@ static void get_remote_object_list(unsigned char parent) enum XML_Status result; struct curl_slist *dav_headers = NULL; struct xml_ctx ctx; - char path[] = "/objects/XX/"; - static const char hex[] = "0123456789abcdef"; - unsigned int val = parent; + struct remote_ls_ctx ls; + + ls.flags = flags; + ls.path = strdup(path); + ls.dentry_name = NULL; + ls.dentry_flags = 0; + ls.userData = userData; + ls.userFunc = userFunc; - path[9] = hex[val >> 4]; - path[10] = hex[val & 0xf]; - url = xmalloc(strlen(remote->url) + strlen(path) + 1); sprintf(url, "%s%s", remote->url, path); out_buffer.size = strlen(PROPFIND_ALL_REQUEST); @@ -1203,6 +1553,7 @@ static void get_remote_object_list(unsigned char parent) dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); @@ -1215,12 +1566,12 @@ static void get_remote_object_list(unsigned char parent) if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result == CURLE_OK) { - remote_dir_exists[parent] = 1; + if (results.curl_result == CURLE_OK) { ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; - ctx.userFunc = handle_remote_object_list_ctx; + ctx.userFunc = handle_remote_ls_ctx; + ctx.userData = &ls; XML_SetUserData(parser, &ctx); XML_SetElementHandler(parser, xml_start_tag, xml_end_tag); @@ -1234,22 +1585,35 @@ static void get_remote_object_list(unsigned char parent) XML_ErrorString( XML_GetErrorCode(parser))); } - } else { - remote_dir_exists[parent] = 0; } } else { - fprintf(stderr, "Unable to start request\n"); + fprintf(stderr, "Unable to start PROPFIND request\n"); } + free(ls.path); free(url); free(out_data); free(in_buffer.buffer); curl_slist_free_all(dav_headers); } +static void get_remote_object_list(unsigned char parent) +{ + char path[] = "objects/XX/"; + static const char hex[] = "0123456789abcdef"; + unsigned int val = parent; + + path[8] = hex[val >> 4]; + path[9] = hex[val & 0xf]; + remote_dir_exists[val] = 0; + remote_ls(path, (PROCESS_FILES | PROCESS_DIRS), + process_ls_object, &val); +} + static int locking_available(void) { struct active_request_slot *slot; + struct slot_results results; struct buffer in_buffer; struct buffer out_buffer; char *in_data; @@ -1276,8 +1640,9 @@ static int locking_available(void) dav_headers = curl_slist_append(dav_headers, "Depth: 0"); dav_headers = curl_slist_append(dav_headers, "Content-Type: text/xml"); - + slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); @@ -1290,7 +1655,7 @@ static int locking_available(void) if (start_active_slot(slot)) { run_active_slot(slot); - if (slot->curl_result == CURLE_OK) { + if (results.curl_result == CURLE_OK) { ctx.name = xcalloc(10, 1); ctx.len = 0; ctx.cdata = NULL; @@ -1311,7 +1676,7 @@ static int locking_available(void) } } } else { - fprintf(stderr, "Unable to start request\n"); + fprintf(stderr, "Unable to start PROPFIND request\n"); } free(out_data); @@ -1372,16 +1737,17 @@ static struct object_list **process_tree(struct tree *tree, return p; } -static void get_delta(struct rev_info *revs, struct remote_lock *lock) +static int get_delta(struct rev_info *revs, struct remote_lock *lock) { struct commit *commit; struct object_list **p = &objects, *pending; + int count = 0; while ((commit = get_revision(revs)) != NULL) { p = process_tree(commit->tree, p, NULL, ""); commit->object.flags |= LOCAL; if (!(commit->object.flags & UNINTERESTING)) - add_request(&commit->object, lock); + count += add_send_request(&commit->object, lock); } for (pending = revs->pending_objects; pending; pending = pending->next) { @@ -1408,14 +1774,17 @@ static void get_delta(struct rev_info *revs, struct remote_lock *lock) while (objects) { if (!(objects->item->flags & UNINTERESTING)) - add_request(objects->item, lock); + count += add_send_request(objects->item, lock); objects = objects->next; } + + return count; } static int update_remote(unsigned char *sha1, struct remote_lock *lock) { struct active_request_slot *slot; + struct slot_results results; char *out_data; char *if_header; struct buffer out_buffer; @@ -1437,6 +1806,7 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) out_buffer.buffer = out_data; slot = get_active_slot(); + slot->results = &results; curl_easy_setopt(slot->curl, CURLOPT_INFILE, &out_buffer); curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, out_buffer.size); curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); @@ -1451,10 +1821,10 @@ static int update_remote(unsigned char *sha1, struct remote_lock *lock) run_active_slot(slot); free(out_data); free(if_header); - if (slot->curl_result != CURLE_OK) { + if (results.curl_result != CURLE_OK) { fprintf(stderr, "PUT error: curl result=%d, HTTP code=%ld\n", - slot->curl_result, slot->http_code); + results.curl_result, results.http_code); /* We should attempt recovery? */ return 0; } @@ -1487,6 +1857,7 @@ static void one_remote_ref(char *refname) { struct ref *ref; unsigned char remote_sha1[20]; + struct object *obj; if (fetch_ref(refname, remote_sha1) != 0) { fprintf(stderr, @@ -1495,6 +1866,19 @@ static void one_remote_ref(char *refname) return; } + /* + * Fetch a copy of the object if it doesn't exist locally - it + * may be required for updating server info later. + */ + if (remote->can_update_info_refs && !has_sha1_file(remote_sha1)) { + obj = lookup_unknown_object(remote_sha1); + if (obj) { + fprintf(stderr, " fetch %s for %s\n", + sha1_to_hex(remote_sha1), refname); + add_fetch_request(obj); + } + } + int len = strlen(refname) + 1; ref = xcalloc(1, sizeof(*ref) + len); memcpy(ref->old_sha1, remote_sha1, 20); @@ -1512,7 +1896,7 @@ static void get_local_heads(void) static void get_dav_remote_heads(void) { remote_tail = &remote_refs; - crawl_remote_refs("refs/"); + remote_ls("refs/", (PROCESS_FILES | PROCESS_DIRS | RECURSIVE), process_ls_ref, NULL); } static int is_zero_sha1(const unsigned char *sha1) @@ -1600,24 +1984,142 @@ static void mark_edges_uninteresting(struct commit_list *list) } } +static void add_remote_info_ref(struct remote_ls_ctx *ls) +{ + struct buffer *buf = (struct buffer *)ls->userData; + unsigned char remote_sha1[20]; + struct object *o; + int len; + char *ref_info; + + if (fetch_ref(ls->dentry_name, remote_sha1) != 0) { + fprintf(stderr, + "Unable to fetch ref %s from %s\n", + ls->dentry_name, remote->url); + aborted = 1; + return; + } + + o = parse_object(remote_sha1); + if (!o) { + fprintf(stderr, + "Unable to parse object %s for remote ref %s\n", + sha1_to_hex(remote_sha1), ls->dentry_name); + aborted = 1; + return; + } + + len = strlen(ls->dentry_name) + 42; + ref_info = xcalloc(len + 1, 1); + sprintf(ref_info, "%s %s\n", + sha1_to_hex(remote_sha1), ls->dentry_name); + fwrite_buffer(ref_info, 1, len, buf); + free(ref_info); + + if (o->type == tag_type) { + o = deref_tag(o, ls->dentry_name, 0); + if (o) { + len = strlen(ls->dentry_name) + 45; + ref_info = xcalloc(len + 1, 1); + sprintf(ref_info, "%s %s^{}\n", + sha1_to_hex(o->sha1), ls->dentry_name); + fwrite_buffer(ref_info, 1, len, buf); + free(ref_info); + } + } +} + +static void update_remote_info_refs(struct remote_lock *lock) +{ + struct buffer buffer; + struct active_request_slot *slot; + struct slot_results results; + char *if_header; + struct curl_slist *dav_headers = NULL; + + buffer.buffer = xmalloc(4096); + memset(buffer.buffer, 0, 4096); + buffer.size = 4096; + buffer.posn = 0; + remote_ls("refs/", (PROCESS_FILES | RECURSIVE), + add_remote_info_ref, &buffer); + if (!aborted) { + if_header = xmalloc(strlen(lock->token) + 25); + sprintf(if_header, "If: (<opaquelocktoken:%s>)", lock->token); + dav_headers = curl_slist_append(dav_headers, if_header); + + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_INFILE, &buffer); + curl_easy_setopt(slot->curl, CURLOPT_INFILESIZE, buffer.posn); + curl_easy_setopt(slot->curl, CURLOPT_READFUNCTION, fread_buffer); + curl_easy_setopt(slot->curl, CURLOPT_WRITEFUNCTION, fwrite_null); + curl_easy_setopt(slot->curl, CURLOPT_CUSTOMREQUEST, DAV_PUT); + curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, dav_headers); + curl_easy_setopt(slot->curl, CURLOPT_UPLOAD, 1); + curl_easy_setopt(slot->curl, CURLOPT_PUT, 1); + curl_easy_setopt(slot->curl, CURLOPT_URL, lock->url); + + buffer.posn = 0; + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.curl_result != CURLE_OK) { + fprintf(stderr, + "PUT error: curl result=%d, HTTP code=%ld\n", + results.curl_result, results.http_code); + } + } + free(if_header); + } + free(buffer.buffer); +} + +static int remote_exists(const char *path) +{ + char *url = xmalloc(strlen(remote->url) + strlen(path) + 1); + struct active_request_slot *slot; + struct slot_results results; + + sprintf(url, "%s%s", remote->url, path); + + slot = get_active_slot(); + slot->results = &results; + curl_easy_setopt(slot->curl, CURLOPT_URL, url); + curl_easy_setopt(slot->curl, CURLOPT_NOBODY, 1); + + if (start_active_slot(slot)) { + run_active_slot(slot); + if (results.http_code == 404) + return 0; + else if (results.curl_result == CURLE_OK) + return 1; + else + fprintf(stderr, "HEAD HTTP error %ld\n", results.http_code); + } else { + fprintf(stderr, "Unable to start HEAD request\n"); + } + + return -1; +} + int main(int argc, char **argv) { struct transfer_request *request; struct transfer_request *next_request; int nr_refspec = 0; char **refspec = NULL; - struct remote_lock *ref_lock; + struct remote_lock *ref_lock = NULL; + struct remote_lock *info_ref_lock = NULL; struct rev_info revs; + int objects_to_send; int rc = 0; int i; setup_git_directory(); setup_ident(); - remote = xmalloc(sizeof(*remote)); - remote->url = NULL; - remote->path_len = 0; - remote->packs = NULL; + remote = xcalloc(sizeof(*remote), 1); argv++; for (i = 1; i < argc; i++, argv++) { @@ -1674,6 +2176,18 @@ int main(int argc, char **argv) goto cleanup; } + /* Check whether the remote has server info files */ + remote->can_update_info_refs = 0; + remote->has_info_refs = remote_exists("info/refs"); + remote->has_info_packs = remote_exists("objects/info/packs"); + if (remote->has_info_refs) { + info_ref_lock = lock_remote("info/refs", LOCK_TIME); + if (info_ref_lock) + remote->can_update_info_refs = 1; + } + if (remote->has_info_packs) + fetch_indices(); + /* Get a list of all local and remote heads to validate refspecs */ get_local_heads(); fprintf(stderr, "Fetching remote heads...\n"); @@ -1690,7 +2204,6 @@ int main(int argc, char **argv) return 0; } - int ret = 0; int new_refs = 0; struct ref *ref; for (ref = remote_refs; ref; ref = ref->next) { @@ -1722,14 +2235,14 @@ int main(int argc, char **argv) "need to pull first?", ref->name, ref->peer_ref->name); - ret = -2; + rc = -2; continue; } } memcpy(ref->new_sha1, ref->peer_ref->new_sha1, 20); if (is_zero_sha1(ref->new_sha1)) { error("cannot happen anymore"); - ret = -3; + rc = -3; continue; } new_refs++; @@ -1752,23 +2265,20 @@ int main(int argc, char **argv) } /* Set up revision info for this refspec */ - const char *commit_argv[3]; - int commit_argc = 2; + const char *commit_argv[4]; + int commit_argc = 3; char *new_sha1_hex = strdup(sha1_to_hex(ref->new_sha1)); char *old_sha1_hex = NULL; - commit_argv[1] = new_sha1_hex; + commit_argv[1] = "--objects"; + commit_argv[2] = new_sha1_hex; if (!push_all && !is_zero_sha1(ref->old_sha1)) { old_sha1_hex = xmalloc(42); sprintf(old_sha1_hex, "^%s", sha1_to_hex(ref->old_sha1)); - commit_argv[2] = old_sha1_hex; + commit_argv[3] = old_sha1_hex; commit_argc++; } - revs.commits = NULL; setup_revisions(commit_argc, commit_argv, &revs, NULL); - revs.tag_objects = 1; - revs.tree_objects = 1; - revs.blob_objects = 1; free(new_sha1_hex); if (old_sha1_hex) { free(old_sha1_hex); @@ -1779,13 +2289,15 @@ int main(int argc, char **argv) pushing = 0; prepare_revision_walk(&revs); mark_edges_uninteresting(revs.commits); - fetch_indices(); - get_delta(&revs, ref_lock); + objects_to_send = get_delta(&revs, ref_lock); finish_all_active_slots(); /* Push missing objects to remote, this would be a convenient time to pack them first if appropriate. */ pushing = 1; + if (objects_to_send) + fprintf(stderr, " sending %d objects\n", + objects_to_send); fill_active_slots(); finish_all_active_slots(); @@ -1799,7 +2311,20 @@ int main(int argc, char **argv) if (!rc) fprintf(stderr, " done\n"); unlock_remote(ref_lock); + check_locks(); + } + + /* Update remote server info if appropriate */ + if (remote->has_info_refs && new_refs) { + if (info_ref_lock && remote->can_update_info_refs) { + fprintf(stderr, "Updating remote server info\n"); + update_remote_info_refs(info_ref_lock); + } else { + fprintf(stderr, "Unable to update server info\n"); + } } + if (info_ref_lock) + unlock_remote(info_ref_lock); cleanup: free(remote); @@ -339,6 +339,7 @@ struct active_request_slot *get_active_slot(void) slot->in_use = 1; slot->local = NULL; slot->results = NULL; + slot->finished = NULL; slot->callback_data = NULL; slot->callback_func = NULL; curl_easy_setopt(slot->curl, CURLOPT_HTTPHEADER, pragma_header); @@ -389,8 +390,10 @@ void run_active_slot(struct active_request_slot *slot) fd_set excfds; int max_fd; struct timeval select_timeout; + int finished = 0; - while (slot->in_use) { + slot->finished = &finished; + while (!finished) { data_received = 0; step_active_slots(); @@ -442,6 +445,9 @@ static void finish_active_slot(struct active_request_slot *slot) closedown_active_slot(slot); curl_easy_getinfo(slot->curl, CURLINFO_HTTP_CODE, &slot->http_code); + if (slot->finished != NULL) + (*slot->finished) = 1; + /* Store slot results so they can be read after the slot is reused */ if (slot->results != NULL) { slot->results->curl_result = slot->curl_result; @@ -35,6 +35,7 @@ struct active_request_slot int in_use; CURLcode curl_result; long http_code; + int *finished; struct slot_results *results; void *callback_data; void (*callback_func)(void *data); diff --git a/imap-send.c b/imap-send.c new file mode 100644 index 0000000000..1b38b3af67 --- /dev/null +++ b/imap-send.c @@ -0,0 +1,1359 @@ +/* + * git-imap-send - drops patches into an imap Drafts folder + * derived from isync/mbsync - mailbox synchronizer + * + * Copyright (C) 2000-2002 Michael R. Elkins <me@mutt.org> + * Copyright (C) 2002-2004 Oswald Buddenhagen <ossi@users.sf.net> + * Copyright (C) 2004 Theodore Y. Ts'o <tytso@mit.edu> + * Copyright (C) 2006 Mike McCormack + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + */ + +#include "cache.h" + +#include <assert.h> +#include <netinet/in.h> +#include <netinet/tcp.h> +#include <arpa/inet.h> +#include <sys/socket.h> +#include <netdb.h> + +typedef struct store_conf { + char *name; + const char *path; /* should this be here? its interpretation is driver-specific */ + char *map_inbox; + char *trash; + unsigned max_size; /* off_t is overkill */ + unsigned trash_remote_new:1, trash_only_new:1; +} store_conf_t; + +typedef struct string_list { + struct string_list *next; + char string[1]; +} string_list_t; + +typedef struct channel_conf { + struct channel_conf *next; + char *name; + store_conf_t *master, *slave; + char *master_name, *slave_name; + char *sync_state; + string_list_t *patterns; + int mops, sops; + unsigned max_messages; /* for slave only */ +} channel_conf_t; + +typedef struct group_conf { + struct group_conf *next; + char *name; + string_list_t *channels; +} group_conf_t; + +/* For message->status */ +#define M_RECENT (1<<0) /* unsyncable flag; maildir_* depend on this being 1<<0 */ +#define M_DEAD (1<<1) /* expunged */ +#define M_FLAGS (1<<2) /* flags fetched */ + +typedef struct message { + struct message *next; + /* string_list_t *keywords; */ + size_t size; /* zero implies "not fetched" */ + int uid; + unsigned char flags, status; +} message_t; + +typedef struct store { + store_conf_t *conf; /* foreign */ + + /* currently open mailbox */ + const char *name; /* foreign! maybe preset? */ + char *path; /* own */ + message_t *msgs; /* own */ + int uidvalidity; + unsigned char opts; /* maybe preset? */ + /* note that the following do _not_ reflect stats from msgs, but mailbox totals */ + int count; /* # of messages */ + int recent; /* # of recent messages - don't trust this beyond the initial read */ +} store_t; + +typedef struct { + char *data; + int len; + unsigned char flags; + unsigned char crlf:1; +} msg_data_t; + +#define DRV_OK 0 +#define DRV_MSG_BAD -1 +#define DRV_BOX_BAD -2 +#define DRV_STORE_BAD -3 + +static int Verbose, Quiet; + +static void info( const char *, ... ); +static void warn( const char *, ... ); + +static char *next_arg( char ** ); + +static void free_generic_messages( message_t * ); + +static int nfvasprintf( char **str, const char *fmt, va_list va ); +static int nfsnprintf( char *buf, int blen, const char *fmt, ... ); + + +static void arc4_init( void ); +static unsigned char arc4_getbyte( void ); + +typedef struct imap_server_conf { + char *name; + char *tunnel; + char *host; + int port; + char *user; + char *pass; +} imap_server_conf_t; + +typedef struct imap_store_conf { + store_conf_t gen; + imap_server_conf_t *server; + unsigned use_namespace:1; +} imap_store_conf_t; + +#define NIL (void*)0x1 +#define LIST (void*)0x2 + +typedef struct _list { + struct _list *next, *child; + char *val; + int len; +} list_t; + +typedef struct { + int fd; +} Socket_t; + +typedef struct { + Socket_t sock; + int bytes; + int offset; + char buf[1024]; +} buffer_t; + +struct imap_cmd; + +typedef struct imap { + int uidnext; /* from SELECT responses */ + list_t *ns_personal, *ns_other, *ns_shared; /* NAMESPACE info */ + unsigned caps, rcaps; /* CAPABILITY results */ + /* command queue */ + int nexttag, num_in_progress, literal_pending; + struct imap_cmd *in_progress, **in_progress_append; + buffer_t buf; /* this is BIG, so put it last */ +} imap_t; + +typedef struct imap_store { + store_t gen; + int uidvalidity; + imap_t *imap; + const char *prefix; + unsigned /*currentnc:1,*/ trashnc:1; +} imap_store_t; + +struct imap_cmd_cb { + int (*cont)( imap_store_t *ctx, struct imap_cmd *cmd, const char *prompt ); + void (*done)( imap_store_t *ctx, struct imap_cmd *cmd, int response); + void *ctx; + char *data; + int dlen; + int uid; + unsigned create:1, trycreate:1; +}; + +struct imap_cmd { + struct imap_cmd *next; + struct imap_cmd_cb cb; + char *cmd; + int tag; +}; + +#define CAP(cap) (imap->caps & (1 << (cap))) + +enum CAPABILITY { + NOLOGIN = 0, + UIDPLUS, + LITERALPLUS, + NAMESPACE, +}; + +static const char *cap_list[] = { + "LOGINDISABLED", + "UIDPLUS", + "LITERAL+", + "NAMESPACE", +}; + +#define RESP_OK 0 +#define RESP_NO 1 +#define RESP_BAD 2 + +static int get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ); + + +static const char *Flags[] = { + "Draft", + "Flagged", + "Answered", + "Seen", + "Deleted", +}; + +static void +socket_perror( const char *func, Socket_t *sock, int ret ) +{ + if (ret < 0) + perror( func ); + else + fprintf( stderr, "%s: unexpected EOF\n", func ); +} + +static int +socket_read( Socket_t *sock, char *buf, int len ) +{ + int n = read( sock->fd, buf, len ); + if (n <= 0) { + socket_perror( "read", sock, n ); + close( sock->fd ); + sock->fd = -1; + } + return n; +} + +static int +socket_write( Socket_t *sock, char *buf, int len ) +{ + int n = write( sock->fd, buf, len ); + if (n != len) { + socket_perror( "write", sock, n ); + close( sock->fd ); + sock->fd = -1; + } + return n; +} + +/* simple line buffering */ +static int +buffer_gets( buffer_t * b, char **s ) +{ + int n; + int start = b->offset; + + *s = b->buf + start; + + for (;;) { + /* make sure we have enough data to read the \r\n sequence */ + if (b->offset + 1 >= b->bytes) { + if (start) { + /* shift down used bytes */ + *s = b->buf; + + assert( start <= b->bytes ); + n = b->bytes - start; + + if (n) + memcpy( b->buf, b->buf + start, n ); + b->offset -= start; + b->bytes = n; + start = 0; + } + + n = socket_read( &b->sock, b->buf + b->bytes, + sizeof(b->buf) - b->bytes ); + + if (n <= 0) + return -1; + + b->bytes += n; + } + + if (b->buf[b->offset] == '\r') { + assert( b->offset + 1 < b->bytes ); + if (b->buf[b->offset + 1] == '\n') { + b->buf[b->offset] = 0; /* terminate the string */ + b->offset += 2; /* next line */ + if (Verbose) + puts( *s ); + return 0; + } + } + + b->offset++; + } + /* not reached */ +} + +static void +info( const char *msg, ... ) +{ + va_list va; + + if (!Quiet) { + va_start( va, msg ); + vprintf( msg, va ); + va_end( va ); + fflush( stdout ); + } +} + +static void +warn( const char *msg, ... ) +{ + va_list va; + + if (Quiet < 2) { + va_start( va, msg ); + vfprintf( stderr, msg, va ); + va_end( va ); + } +} + +static char * +next_arg( char **s ) +{ + char *ret; + + if (!s || !*s) + return 0; + while (isspace( (unsigned char) **s )) + (*s)++; + if (!**s) { + *s = 0; + return 0; + } + if (**s == '"') { + ++*s; + ret = *s; + *s = strchr( *s, '"' ); + } else { + ret = *s; + while (**s && !isspace( (unsigned char) **s )) + (*s)++; + } + if (*s) { + if (**s) + *(*s)++ = 0; + if (!**s) + *s = 0; + } + return ret; +} + +static void +free_generic_messages( message_t *msgs ) +{ + message_t *tmsg; + + for (; msgs; msgs = tmsg) { + tmsg = msgs->next; + free( msgs ); + } +} + +static int +vasprintf( char **strp, const char *fmt, va_list ap ) +{ + int len; + char tmp[1024]; + + if ((len = vsnprintf( tmp, sizeof(tmp), fmt, ap )) < 0 || !(*strp = xmalloc( len + 1 ))) + return -1; + if (len >= (int)sizeof(tmp)) + vsprintf( *strp, fmt, ap ); + else + memcpy( *strp, tmp, len + 1 ); + return len; +} + +static int +nfsnprintf( char *buf, int blen, const char *fmt, ... ) +{ + int ret; + va_list va; + + va_start( va, fmt ); + if (blen <= 0 || (unsigned)(ret = vsnprintf( buf, blen, fmt, va )) >= (unsigned)blen) + die( "Fatal: buffer too small. Please report a bug.\n"); + va_end( va ); + return ret; +} + +static int +nfvasprintf( char **str, const char *fmt, va_list va ) +{ + int ret = vasprintf( str, fmt, va ); + if (ret < 0) + die( "Fatal: Out of memory\n"); + return ret; +} + +static struct { + unsigned char i, j, s[256]; +} rs; + +static void +arc4_init( void ) +{ + int i, fd; + unsigned char j, si, dat[128]; + + if ((fd = open( "/dev/urandom", O_RDONLY )) < 0 && (fd = open( "/dev/random", O_RDONLY )) < 0) { + fprintf( stderr, "Fatal: no random number source available.\n" ); + exit( 3 ); + } + if (read( fd, dat, 128 ) != 128) { + fprintf( stderr, "Fatal: cannot read random number source.\n" ); + exit( 3 ); + } + close( fd ); + + for (i = 0; i < 256; i++) + rs.s[i] = i; + for (i = j = 0; i < 256; i++) { + si = rs.s[i]; + j += si + dat[i & 127]; + rs.s[i] = rs.s[j]; + rs.s[j] = si; + } + rs.i = rs.j = 0; + + for (i = 0; i < 256; i++) + arc4_getbyte(); +} + +static unsigned char +arc4_getbyte( void ) +{ + unsigned char si, sj; + + rs.i++; + si = rs.s[rs.i]; + rs.j += si; + sj = rs.s[rs.j]; + rs.s[rs.i] = sj; + rs.s[rs.j] = si; + return rs.s[(si + sj) & 0xff]; +} + +static struct imap_cmd * +v_issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, + const char *fmt, va_list ap ) +{ + imap_t *imap = ctx->imap; + struct imap_cmd *cmd; + int n, bufl; + char buf[1024]; + + cmd = xmalloc( sizeof(struct imap_cmd) ); + nfvasprintf( &cmd->cmd, fmt, ap ); + cmd->tag = ++imap->nexttag; + + if (cb) + cmd->cb = *cb; + else + memset( &cmd->cb, 0, sizeof(cmd->cb) ); + + while (imap->literal_pending) + get_cmd_result( ctx, 0 ); + + bufl = nfsnprintf( buf, sizeof(buf), cmd->cb.data ? CAP(LITERALPLUS) ? + "%d %s{%d+}\r\n" : "%d %s{%d}\r\n" : "%d %s\r\n", + cmd->tag, cmd->cmd, cmd->cb.dlen ); + if (Verbose) { + if (imap->num_in_progress) + printf( "(%d in progress) ", imap->num_in_progress ); + if (memcmp( cmd->cmd, "LOGIN", 5 )) + printf( ">>> %s", buf ); + else + printf( ">>> %d LOGIN <user> <pass>\n", cmd->tag ); + } + if (socket_write( &imap->buf.sock, buf, bufl ) != bufl) { + free( cmd->cmd ); + free( cmd ); + if (cb && cb->data) + free( cb->data ); + return NULL; + } + if (cmd->cb.data) { + if (CAP(LITERALPLUS)) { + n = socket_write( &imap->buf.sock, cmd->cb.data, cmd->cb.dlen ); + free( cmd->cb.data ); + if (n != cmd->cb.dlen || + (n = socket_write( &imap->buf.sock, "\r\n", 2 )) != 2) + { + free( cmd->cmd ); + free( cmd ); + return NULL; + } + cmd->cb.data = 0; + } else + imap->literal_pending = 1; + } else if (cmd->cb.cont) + imap->literal_pending = 1; + cmd->next = 0; + *imap->in_progress_append = cmd; + imap->in_progress_append = &cmd->next; + imap->num_in_progress++; + return cmd; +} + +static struct imap_cmd * +issue_imap_cmd( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + struct imap_cmd *ret; + va_list ap; + + va_start( ap, fmt ); + ret = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + return ret; +} + +static int +imap_exec( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + va_list ap; + struct imap_cmd *cmdp; + + va_start( ap, fmt ); + cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + if (!cmdp) + return RESP_BAD; + + return get_cmd_result( ctx, cmdp ); +} + +static int +imap_exec_m( imap_store_t *ctx, struct imap_cmd_cb *cb, const char *fmt, ... ) +{ + va_list ap; + struct imap_cmd *cmdp; + + va_start( ap, fmt ); + cmdp = v_issue_imap_cmd( ctx, cb, fmt, ap ); + va_end( ap ); + if (!cmdp) + return DRV_STORE_BAD; + + switch (get_cmd_result( ctx, cmdp )) { + case RESP_BAD: return DRV_STORE_BAD; + case RESP_NO: return DRV_MSG_BAD; + default: return DRV_OK; + } +} + +static int +is_atom( list_t *list ) +{ + return list && list->val && list->val != NIL && list->val != LIST; +} + +static int +is_list( list_t *list ) +{ + return list && list->val == LIST; +} + +static void +free_list( list_t *list ) +{ + list_t *tmp; + + for (; list; list = tmp) { + tmp = list->next; + if (is_list( list )) + free_list( list->child ); + else if (is_atom( list )) + free( list->val ); + free( list ); + } +} + +static int +parse_imap_list_l( imap_t *imap, char **sp, list_t **curp, int level ) +{ + list_t *cur; + char *s = *sp, *p; + int n, bytes; + + for (;;) { + while (isspace( (unsigned char)*s )) + s++; + if (level && *s == ')') { + s++; + break; + } + *curp = cur = xmalloc( sizeof(*cur) ); + curp = &cur->next; + cur->val = 0; /* for clean bail */ + if (*s == '(') { + /* sublist */ + s++; + cur->val = LIST; + if (parse_imap_list_l( imap, &s, &cur->child, level + 1 )) + goto bail; + } else if (imap && *s == '{') { + /* literal */ + bytes = cur->len = strtol( s + 1, &s, 10 ); + if (*s != '}') + goto bail; + + s = cur->val = xmalloc( cur->len ); + + /* dump whats left over in the input buffer */ + n = imap->buf.bytes - imap->buf.offset; + + if (n > bytes) + /* the entire message fit in the buffer */ + n = bytes; + + memcpy( s, imap->buf.buf + imap->buf.offset, n ); + s += n; + bytes -= n; + + /* mark that we used part of the buffer */ + imap->buf.offset += n; + + /* now read the rest of the message */ + while (bytes > 0) { + if ((n = socket_read (&imap->buf.sock, s, bytes)) <= 0) + goto bail; + s += n; + bytes -= n; + } + + if (buffer_gets( &imap->buf, &s )) + goto bail; + } else if (*s == '"') { + /* quoted string */ + s++; + p = s; + for (; *s != '"'; s++) + if (!*s) + goto bail; + cur->len = s - p; + s++; + cur->val = xmalloc( cur->len + 1 ); + memcpy( cur->val, p, cur->len ); + cur->val[cur->len] = 0; + } else { + /* atom */ + p = s; + for (; *s && !isspace( (unsigned char)*s ); s++) + if (level && *s == ')') + break; + cur->len = s - p; + if (cur->len == 3 && !memcmp ("NIL", p, 3)) + cur->val = NIL; + else { + cur->val = xmalloc( cur->len + 1 ); + memcpy( cur->val, p, cur->len ); + cur->val[cur->len] = 0; + } + } + + if (!level) + break; + if (!*s) + goto bail; + } + *sp = s; + *curp = 0; + return 0; + + bail: + *curp = 0; + return -1; +} + +static list_t * +parse_imap_list( imap_t *imap, char **sp ) +{ + list_t *head; + + if (!parse_imap_list_l( imap, sp, &head, 0 )) + return head; + free_list( head ); + return NULL; +} + +static list_t * +parse_list( char **sp ) +{ + return parse_imap_list( 0, sp ); +} + +static void +parse_capability( imap_t *imap, char *cmd ) +{ + char *arg; + unsigned i; + + imap->caps = 0x80000000; + while ((arg = next_arg( &cmd ))) + for (i = 0; i < ARRAY_SIZE(cap_list); i++) + if (!strcmp( cap_list[i], arg )) + imap->caps |= 1 << i; + imap->rcaps = imap->caps; +} + +static int +parse_response_code( imap_store_t *ctx, struct imap_cmd_cb *cb, char *s ) +{ + imap_t *imap = ctx->imap; + char *arg, *p; + + if (*s != '[') + return RESP_OK; /* no response code */ + s++; + if (!(p = strchr( s, ']' ))) { + fprintf( stderr, "IMAP error: malformed response code\n" ); + return RESP_BAD; + } + *p++ = 0; + arg = next_arg( &s ); + if (!strcmp( "UIDVALIDITY", arg )) { + if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg ))) { + fprintf( stderr, "IMAP error: malformed UIDVALIDITY status\n" ); + return RESP_BAD; + } + } else if (!strcmp( "UIDNEXT", arg )) { + if (!(arg = next_arg( &s )) || !(imap->uidnext = atoi( arg ))) { + fprintf( stderr, "IMAP error: malformed NEXTUID status\n" ); + return RESP_BAD; + } + } else if (!strcmp( "CAPABILITY", arg )) { + parse_capability( imap, s ); + } else if (!strcmp( "ALERT", arg )) { + /* RFC2060 says that these messages MUST be displayed + * to the user + */ + for (; isspace( (unsigned char)*p ); p++); + fprintf( stderr, "*** IMAP ALERT *** %s\n", p ); + } else if (cb && cb->ctx && !strcmp( "APPENDUID", arg )) { + if (!(arg = next_arg( &s )) || !(ctx->gen.uidvalidity = atoi( arg )) || + !(arg = next_arg( &s )) || !(*(int *)cb->ctx = atoi( arg ))) + { + fprintf( stderr, "IMAP error: malformed APPENDUID status\n" ); + return RESP_BAD; + } + } + return RESP_OK; +} + +static int +get_cmd_result( imap_store_t *ctx, struct imap_cmd *tcmd ) +{ + imap_t *imap = ctx->imap; + struct imap_cmd *cmdp, **pcmdp, *ncmdp; + char *cmd, *arg, *arg1, *p; + int n, resp, resp2, tag; + + for (;;) { + if (buffer_gets( &imap->buf, &cmd )) + return RESP_BAD; + + arg = next_arg( &cmd ); + if (*arg == '*') { + arg = next_arg( &cmd ); + if (!arg) { + fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); + return RESP_BAD; + } + + if (!strcmp( "NAMESPACE", arg )) { + imap->ns_personal = parse_list( &cmd ); + imap->ns_other = parse_list( &cmd ); + imap->ns_shared = parse_list( &cmd ); + } else if (!strcmp( "OK", arg ) || !strcmp( "BAD", arg ) || + !strcmp( "NO", arg ) || !strcmp( "BYE", arg )) { + if ((resp = parse_response_code( ctx, 0, cmd )) != RESP_OK) + return resp; + } else if (!strcmp( "CAPABILITY", arg )) + parse_capability( imap, cmd ); + else if ((arg1 = next_arg( &cmd ))) { + if (!strcmp( "EXISTS", arg1 )) + ctx->gen.count = atoi( arg ); + else if (!strcmp( "RECENT", arg1 )) + ctx->gen.recent = atoi( arg ); + } else { + fprintf( stderr, "IMAP error: unable to parse untagged response\n" ); + return RESP_BAD; + } + } else if (!imap->in_progress) { + fprintf( stderr, "IMAP error: unexpected reply: %s %s\n", arg, cmd ? cmd : "" ); + return RESP_BAD; + } else if (*arg == '+') { + /* This can happen only with the last command underway, as + it enforces a round-trip. */ + cmdp = (struct imap_cmd *)((char *)imap->in_progress_append - + offsetof(struct imap_cmd, next)); + if (cmdp->cb.data) { + n = socket_write( &imap->buf.sock, cmdp->cb.data, cmdp->cb.dlen ); + free( cmdp->cb.data ); + cmdp->cb.data = 0; + if (n != (int)cmdp->cb.dlen) + return RESP_BAD; + } else if (cmdp->cb.cont) { + if (cmdp->cb.cont( ctx, cmdp, cmd )) + return RESP_BAD; + } else { + fprintf( stderr, "IMAP error: unexpected command continuation request\n" ); + return RESP_BAD; + } + if (socket_write( &imap->buf.sock, "\r\n", 2 ) != 2) + return RESP_BAD; + if (!cmdp->cb.cont) + imap->literal_pending = 0; + if (!tcmd) + return DRV_OK; + } else { + tag = atoi( arg ); + for (pcmdp = &imap->in_progress; (cmdp = *pcmdp); pcmdp = &cmdp->next) + if (cmdp->tag == tag) + goto gottag; + fprintf( stderr, "IMAP error: unexpected tag %s\n", arg ); + return RESP_BAD; + gottag: + if (!(*pcmdp = cmdp->next)) + imap->in_progress_append = pcmdp; + imap->num_in_progress--; + if (cmdp->cb.cont || cmdp->cb.data) + imap->literal_pending = 0; + arg = next_arg( &cmd ); + if (!strcmp( "OK", arg )) + resp = DRV_OK; + else { + if (!strcmp( "NO", arg )) { + if (cmdp->cb.create && cmd && (cmdp->cb.trycreate || !memcmp( cmd, "[TRYCREATE]", 11 ))) { /* SELECT, APPEND or UID COPY */ + p = strchr( cmdp->cmd, '"' ); + if (!issue_imap_cmd( ctx, 0, "CREATE \"%.*s\"", strchr( p + 1, '"' ) - p + 1, p )) { + resp = RESP_BAD; + goto normal; + } + /* not waiting here violates the spec, but a server that does not + grok this nonetheless violates it too. */ + cmdp->cb.create = 0; + if (!(ncmdp = issue_imap_cmd( ctx, &cmdp->cb, "%s", cmdp->cmd ))) { + resp = RESP_BAD; + goto normal; + } + free( cmdp->cmd ); + free( cmdp ); + if (!tcmd) + return 0; /* ignored */ + if (cmdp == tcmd) + tcmd = ncmdp; + continue; + } + resp = RESP_NO; + } else /*if (!strcmp( "BAD", arg ))*/ + resp = RESP_BAD; + fprintf( stderr, "IMAP command '%s' returned response (%s) - %s\n", + memcmp (cmdp->cmd, "LOGIN", 5) ? + cmdp->cmd : "LOGIN <user> <pass>", + arg, cmd ? cmd : ""); + } + if ((resp2 = parse_response_code( ctx, &cmdp->cb, cmd )) > resp) + resp = resp2; + normal: + if (cmdp->cb.done) + cmdp->cb.done( ctx, cmdp, resp ); + if (cmdp->cb.data) + free( cmdp->cb.data ); + free( cmdp->cmd ); + free( cmdp ); + if (!tcmd || tcmd == cmdp) + return resp; + } + } + /* not reached */ +} + +static void +imap_close_server( imap_store_t *ictx ) +{ + imap_t *imap = ictx->imap; + + if (imap->buf.sock.fd != -1) { + imap_exec( ictx, 0, "LOGOUT" ); + close( imap->buf.sock.fd ); + } + free_list( imap->ns_personal ); + free_list( imap->ns_other ); + free_list( imap->ns_shared ); + free( imap ); +} + +static void +imap_close_store( store_t *ctx ) +{ + imap_close_server( (imap_store_t *)ctx ); + free_generic_messages( ctx->msgs ); + free( ctx ); +} + +static store_t * +imap_open_store( imap_server_conf_t *srvc ) +{ + imap_store_t *ctx; + imap_t *imap; + char *arg, *rsp; + struct hostent *he; + struct sockaddr_in addr; + int s, a[2], preauth; + + ctx = xcalloc( sizeof(*ctx), 1 ); + + ctx->imap = imap = xcalloc( sizeof(*imap), 1 ); + imap->buf.sock.fd = -1; + imap->in_progress_append = &imap->in_progress; + + /* open connection to IMAP server */ + + if (srvc->tunnel) { + info( "Starting tunnel '%s'... ", srvc->tunnel ); + + if (socketpair( PF_UNIX, SOCK_STREAM, 0, a )) { + perror( "socketpair" ); + exit( 1 ); + } + + if (fork() == 0) { + if (dup2( a[0], 0 ) == -1 || dup2( a[0], 1 ) == -1) + _exit( 127 ); + close( a[0] ); + close( a[1] ); + execl( "/bin/sh", "sh", "-c", srvc->tunnel, NULL ); + _exit( 127 ); + } + + close (a[0]); + + imap->buf.sock.fd = a[1]; + + info( "ok\n" ); + } else { + memset( &addr, 0, sizeof(addr) ); + addr.sin_port = htons( srvc->port ); + addr.sin_family = AF_INET; + + info( "Resolving %s... ", srvc->host ); + he = gethostbyname( srvc->host ); + if (!he) { + perror( "gethostbyname" ); + goto bail; + } + info( "ok\n" ); + + addr.sin_addr.s_addr = *((int *) he->h_addr_list[0]); + + s = socket( PF_INET, SOCK_STREAM, 0 ); + + info( "Connecting to %s:%hu... ", inet_ntoa( addr.sin_addr ), ntohs( addr.sin_port ) ); + if (connect( s, (struct sockaddr *)&addr, sizeof(addr) )) { + close( s ); + perror( "connect" ); + goto bail; + } + info( "ok\n" ); + + imap->buf.sock.fd = s; + + } + + /* read the greeting string */ + if (buffer_gets( &imap->buf, &rsp )) { + fprintf( stderr, "IMAP error: no greeting response\n" ); + goto bail; + } + arg = next_arg( &rsp ); + if (!arg || *arg != '*' || (arg = next_arg( &rsp )) == NULL) { + fprintf( stderr, "IMAP error: invalid greeting response\n" ); + goto bail; + } + preauth = 0; + if (!strcmp( "PREAUTH", arg )) + preauth = 1; + else if (strcmp( "OK", arg ) != 0) { + fprintf( stderr, "IMAP error: unknown greeting response\n" ); + goto bail; + } + parse_response_code( ctx, 0, rsp ); + if (!imap->caps && imap_exec( ctx, 0, "CAPABILITY" ) != RESP_OK) + goto bail; + + if (!preauth) { + + info ("Logging in...\n"); + if (!srvc->user) { + fprintf( stderr, "Skipping server %s, no user\n", srvc->host ); + goto bail; + } + if (!srvc->pass) { + char prompt[80]; + sprintf( prompt, "Password (%s@%s): ", srvc->user, srvc->host ); + arg = getpass( prompt ); + if (!arg) { + perror( "getpass" ); + exit( 1 ); + } + if (!*arg) { + fprintf( stderr, "Skipping account %s@%s, no password\n", srvc->user, srvc->host ); + goto bail; + } + /* + * getpass() returns a pointer to a static buffer. make a copy + * for long term storage. + */ + srvc->pass = strdup( arg ); + } + if (CAP(NOLOGIN)) { + fprintf( stderr, "Skipping account %s@%s, server forbids LOGIN\n", srvc->user, srvc->host ); + goto bail; + } + warn( "*** IMAP Warning *** Password is being sent in the clear\n" ); + if (imap_exec( ctx, 0, "LOGIN \"%s\" \"%s\"", srvc->user, srvc->pass ) != RESP_OK) { + fprintf( stderr, "IMAP error: LOGIN failed\n" ); + goto bail; + } + } /* !preauth */ + + ctx->prefix = ""; + ctx->trashnc = 1; + return (store_t *)ctx; + + bail: + imap_close_store( &ctx->gen ); + return 0; +} + +static int +imap_make_flags( int flags, char *buf ) +{ + const char *s; + unsigned i, d; + + for (i = d = 0; i < ARRAY_SIZE(Flags); i++) + if (flags & (1 << i)) { + buf[d++] = ' '; + buf[d++] = '\\'; + for (s = Flags[i]; *s; s++) + buf[d++] = *s; + } + buf[0] = '('; + buf[d++] = ')'; + return d; +} + +#define TUIDL 8 + +static int +imap_store_msg( store_t *gctx, msg_data_t *data, int *uid ) +{ + imap_store_t *ctx = (imap_store_t *)gctx; + imap_t *imap = ctx->imap; + struct imap_cmd_cb cb; + char *fmap, *buf; + const char *prefix, *box; + int ret, i, j, d, len, extra, nocr; + int start, sbreak = 0, ebreak = 0; + char flagstr[128], tuid[TUIDL * 2 + 1]; + + memset( &cb, 0, sizeof(cb) ); + + fmap = data->data; + len = data->len; + nocr = !data->crlf; + extra = 0, i = 0; + if (!CAP(UIDPLUS) && uid) { + nloop: + start = i; + while (i < len) + if (fmap[i++] == '\n') { + extra += nocr; + if (i - 2 + nocr == start) { + sbreak = ebreak = i - 2 + nocr; + goto mktid; + } + if (!memcmp( fmap + start, "X-TUID: ", 8 )) { + extra -= (ebreak = i) - (sbreak = start) + nocr; + goto mktid; + } + goto nloop; + } + /* invalid message */ + free( fmap ); + return DRV_MSG_BAD; + mktid: + for (j = 0; j < TUIDL; j++) + sprintf( tuid + j * 2, "%02x", arc4_getbyte() ); + extra += 8 + TUIDL * 2 + 2; + } + if (nocr) + for (; i < len; i++) + if (fmap[i] == '\n') + extra++; + + cb.dlen = len + extra; + buf = cb.data = xmalloc( cb.dlen ); + i = 0; + if (!CAP(UIDPLUS) && uid) { + if (nocr) { + for (; i < sbreak; i++) + if (fmap[i] == '\n') { + *buf++ = '\r'; + *buf++ = '\n'; + } else + *buf++ = fmap[i]; + } else { + memcpy( buf, fmap, sbreak ); + buf += sbreak; + } + memcpy( buf, "X-TUID: ", 8 ); + buf += 8; + memcpy( buf, tuid, TUIDL * 2 ); + buf += TUIDL * 2; + *buf++ = '\r'; + *buf++ = '\n'; + i = ebreak; + } + if (nocr) { + for (; i < len; i++) + if (fmap[i] == '\n') { + *buf++ = '\r'; + *buf++ = '\n'; + } else + *buf++ = fmap[i]; + } else + memcpy( buf, fmap + i, len - i ); + + free( fmap ); + + d = 0; + if (data->flags) { + d = imap_make_flags( data->flags, flagstr ); + flagstr[d++] = ' '; + } + flagstr[d] = 0; + + if (!uid) { + box = gctx->conf->trash; + prefix = ctx->prefix; + cb.create = 1; + if (ctx->trashnc) + imap->caps = imap->rcaps & ~(1 << LITERALPLUS); + } else { + box = gctx->name; + prefix = !strcmp( box, "INBOX" ) ? "" : ctx->prefix; + cb.create = 0; + } + cb.ctx = uid; + ret = imap_exec_m( ctx, &cb, "APPEND \"%s%s\" %s", prefix, box, flagstr ); + imap->caps = imap->rcaps; + if (ret != DRV_OK) + return ret; + if (!uid) + ctx->trashnc = 0; + else + gctx->count++; + + return DRV_OK; +} + +#define CHUNKSIZE 0x1000 + +static int +read_message( FILE *f, msg_data_t *msg ) +{ + int len, r; + + memset( msg, 0, sizeof *msg ); + len = CHUNKSIZE; + msg->data = xmalloc( len+1 ); + msg->data[0] = 0; + + while(!feof( f )) { + if (msg->len >= len) { + void *p; + len += CHUNKSIZE; + p = xrealloc(msg->data, len+1); + if (!p) + break; + } + r = fread( &msg->data[msg->len], 1, len - msg->len, f ); + if (r <= 0) + break; + msg->len += r; + } + msg->data[msg->len] = 0; + return msg->len; +} + +static int +count_messages( msg_data_t *msg ) +{ + int count = 0; + char *p = msg->data; + + while (1) { + if (!strncmp( "From ", p, 5 )) { + count++; + p += 5; + } + p = strstr( p+5, "\nFrom "); + if (!p) + break; + p++; + } + return count; +} + +static int +split_msg( msg_data_t *all_msgs, msg_data_t *msg, int *ofs ) +{ + char *p, *data; + + memset( msg, 0, sizeof *msg ); + if (*ofs >= all_msgs->len) + return 0; + + data = &all_msgs->data[ *ofs ]; + msg->len = all_msgs->len - *ofs; + + if (msg->len < 5 || strncmp( data, "From ", 5 )) + return 0; + + p = strstr( data, "\nFrom " ); + if (p) + msg->len = &p[1] - data; + + msg->data = xmalloc( msg->len + 1 ); + if (!msg->data) + return 0; + + memcpy( msg->data, data, msg->len ); + msg->data[ msg->len ] = 0; + + *ofs += msg->len; + return 1; +} + +static imap_server_conf_t server = +{ + NULL, /* name */ + NULL, /* tunnel */ + NULL, /* host */ + 0, /* port */ + NULL, /* user */ + NULL, /* pass */ +}; + +static char *imap_folder; + +static int +git_imap_config(const char *key, const char *val) +{ + char imap_key[] = "imap."; + + if (strncmp( key, imap_key, sizeof imap_key - 1 )) + return 0; + key += sizeof imap_key - 1; + + if (!strcmp( "folder", key )) { + imap_folder = strdup( val ); + } else if (!strcmp( "host", key )) { + { + if (!strncmp( "imap:", val, 5 )) + val += 5; + if (!server.port) + server.port = 143; + } + if (!strncmp( "//", val, 2 )) + val += 2; + server.host = strdup( val ); + } + else if (!strcmp( "user", key )) + server.user = strdup( val ); + else if (!strcmp( "pass", key )) + server.pass = strdup( val ); + else if (!strcmp( "port", key )) + server.port = git_config_int( key, val ); + else if (!strcmp( "tunnel", key )) + server.tunnel = strdup( val ); + return 0; +} + +int +main(int argc, char **argv) +{ + msg_data_t all_msgs, msg; + store_t *ctx = 0; + int uid = 0; + int ofs = 0; + int r; + int total, n = 0; + + /* init the random number generator */ + arc4_init(); + + git_config( git_imap_config ); + + if (!imap_folder) { + fprintf( stderr, "no imap store specified\n" ); + return 1; + } + + /* read the messages */ + if (!read_message( stdin, &all_msgs )) { + fprintf(stderr,"nothing to send\n"); + return 1; + } + + /* write it to the imap server */ + ctx = imap_open_store( &server ); + if (!ctx) { + fprintf( stderr,"failed to open store\n"); + return 1; + } + + total = count_messages( &all_msgs ); + fprintf( stderr, "sending %d message%s\n", total, (total!=1)?"s":"" ); + ctx->name = imap_folder; + while (1) { + unsigned percent = n * 100 / total; + fprintf( stderr, "%4u%% (%d/%d) done\r", percent, n, total ); + if (!split_msg( &all_msgs, &msg, &ofs )) + break; + r = imap_store_msg( ctx, &msg, &uid ); + if (r != DRV_OK) break; + n++; + } + fprintf( stderr,"\n" ); + + imap_close_store( ctx ); + + return 0; +} diff --git a/rev-list.c b/rev-list.c index 8e4d83efba..812d237f47 100644 --- a/rev-list.c +++ b/rev-list.c @@ -190,7 +190,7 @@ static int count_distance(struct commit_list *entry) if (commit->object.flags & (UNINTERESTING | COUNTED)) break; - if (!revs.paths || (commit->object.flags & TREECHANGE)) + if (!revs.prune_fn || (commit->object.flags & TREECHANGE)) nr++; commit->object.flags |= COUNTED; p = commit->parents; @@ -224,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list) nr = 0; p = list; while (p) { - if (!revs.paths || (p->item->object.flags & TREECHANGE)) + if (!revs.prune_fn || (p->item->object.flags & TREECHANGE)) nr++; p = p->next; } @@ -234,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list) for (p = list; p; p = p->next) { int distance; - if (revs.paths && !(p->item->object.flags & TREECHANGE)) + if (revs.prune_fn && !(p->item->object.flags & TREECHANGE)) continue; distance = count_distance(p); diff --git a/revision.c b/revision.c index 713f27e3ce..01386ed6d4 100644 --- a/revision.c +++ b/revision.c @@ -199,31 +199,27 @@ static int everybody_uninteresting(struct commit_list *orig) return 1; } -#define TREE_SAME 0 -#define TREE_NEW 1 -#define TREE_DIFFERENT 2 -static int tree_difference = TREE_SAME; +static int tree_difference = REV_TREE_SAME; static void file_add_remove(struct diff_options *options, int addremove, unsigned mode, const unsigned char *sha1, const char *base, const char *path) { - int diff = TREE_DIFFERENT; + int diff = REV_TREE_DIFFERENT; /* - * Is it an add of a new file? It means that - * the old tree didn't have it at all, so we - * will turn "TREE_SAME" -> "TREE_NEW", but - * leave any "TREE_DIFFERENT" alone (and if - * it already was "TREE_NEW", we'll keep it - * "TREE_NEW" of course). + * Is it an add of a new file? It means that the old tree + * didn't have it at all, so we will turn "REV_TREE_SAME" -> + * "REV_TREE_NEW", but leave any "REV_TREE_DIFFERENT" alone + * (and if it already was "REV_TREE_NEW", we'll keep it + * "REV_TREE_NEW" of course). */ if (addremove == '+') { diff = tree_difference; - if (diff != TREE_SAME) + if (diff != REV_TREE_SAME) return; - diff = TREE_NEW; + diff = REV_TREE_NEW; } tree_difference = diff; } @@ -234,7 +230,7 @@ static void file_change(struct diff_options *options, const unsigned char *new_sha1, const char *base, const char *path) { - tree_difference = TREE_DIFFERENT; + tree_difference = REV_TREE_DIFFERENT; } static struct diff_options diff_opt = { @@ -243,19 +239,19 @@ static struct diff_options diff_opt = { .change = file_change, }; -static int compare_tree(struct tree *t1, struct tree *t2) +int rev_compare_tree(struct tree *t1, struct tree *t2) { if (!t1) - return TREE_NEW; + return REV_TREE_NEW; if (!t2) - return TREE_DIFFERENT; - tree_difference = TREE_SAME; + return REV_TREE_DIFFERENT; + tree_difference = REV_TREE_SAME; if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0) - return TREE_DIFFERENT; + return REV_TREE_DIFFERENT; return tree_difference; } -static int same_tree_as_empty(struct tree *t1) +int rev_same_tree_as_empty(struct tree *t1) { int retval; void *tree; @@ -282,12 +278,13 @@ static int same_tree_as_empty(struct tree *t1) static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) { struct commit_list **pp, *parent; + int tree_changed = 0; if (!commit->tree) return; if (!commit->parents) { - if (!same_tree_as_empty(commit->tree)) + if (!rev_same_tree_as_empty(commit->tree)) commit->object.flags |= TREECHANGE; return; } @@ -296,31 +293,39 @@ static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) while ((parent = *pp) != NULL) { struct commit *p = parent->item; - if (p->object.flags & UNINTERESTING) { - pp = &parent->next; - continue; - } - parse_commit(p); - switch (compare_tree(p->tree, commit->tree)) { - case TREE_SAME: + switch (rev_compare_tree(p->tree, commit->tree)) { + case REV_TREE_SAME: + if (p->object.flags & UNINTERESTING) { + /* Even if a merge with an uninteresting + * side branch brought the entire change + * we are interested in, we do not want + * to lose the other branches of this + * merge, so we just keep going. + */ + pp = &parent->next; + continue; + } parent->next = NULL; commit->parents = parent; return; - case TREE_NEW: - if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) { + case REV_TREE_NEW: + if (revs->remove_empty_trees && + rev_same_tree_as_empty(p->tree)) { *pp = parent->next; continue; } /* fallthrough */ - case TREE_DIFFERENT: + case REV_TREE_DIFFERENT: + tree_changed = 1; pp = &parent->next; continue; } die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1)); } - commit->object.flags |= TREECHANGE; + if (tree_changed) + commit->object.flags |= TREECHANGE; } static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list) @@ -360,8 +365,8 @@ static void add_parents_to_list(struct rev_info *revs, struct commit *commit, st * simplify the commit history and find the parent * that has no differences in the path set if one exists. */ - if (revs->paths) - try_to_simplify_commit(revs, commit); + if (revs->prune_fn) + revs->prune_fn(revs, commit); parent = commit->parents; while (parent) { @@ -383,9 +388,6 @@ static void limit_list(struct rev_info *revs) struct commit_list *newlist = NULL; struct commit_list **p = &newlist; - if (revs->paths) - diff_tree_setup_paths(revs->paths); - while (list) { struct commit_list *entry = list; struct commit *commit = list->item; @@ -437,6 +439,23 @@ static void handle_all(struct rev_info *revs, unsigned flags) for_each_ref(handle_one_ref); } +void init_revisions(struct rev_info *revs) +{ + memset(revs, 0, sizeof(*revs)); + revs->lifo = 1; + revs->dense = 1; + revs->prefix = setup_git_directory(); + revs->max_age = -1; + revs->min_age = -1; + revs->max_count = -1; + + revs->prune_fn = NULL; + revs->prune_data = NULL; + + revs->topo_setter = topo_sort_default_setter; + revs->topo_getter = topo_sort_default_getter; +} + /* * Parse revision information, filling in the "rev_info" structure, * and removing the used arguments from the argument list. @@ -450,13 +469,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch const char **unrecognized = argv + 1; int left = 1; - memset(revs, 0, sizeof(*revs)); - revs->lifo = 1; - revs->dense = 1; - revs->prefix = setup_git_directory(); - revs->max_age = -1; - revs->min_age = -1; - revs->max_count = -1; + init_revisions(revs); /* First, search for "--" */ seen_dashdash = 0; @@ -466,7 +479,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch continue; argv[i] = NULL; argc = i; - revs->paths = get_pathspec(revs->prefix, argv + i + 1); + revs->prune_data = get_pathspec(revs->prefix, argv + i + 1); seen_dashdash = 1; break; } @@ -630,7 +643,7 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch if (lstat(argv[j], &st) < 0) die("'%s': %s", arg, strerror(errno)); } - revs->paths = get_pathspec(revs->prefix, argv + i); + revs->prune_data = get_pathspec(revs->prefix, argv + i); break; } commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags); @@ -644,8 +657,13 @@ int setup_revisions(int argc, const char **argv, struct rev_info *revs, const ch commit = get_commit_reference(revs, def, sha1, 0); add_one_commit(commit, revs); } - if (revs->paths) + + if (revs->prune_data) { + diff_tree_setup_paths(revs->prune_data); + revs->prune_fn = try_to_simplify_commit; revs->limited = 1; + } + return left; } @@ -655,7 +673,9 @@ void prepare_revision_walk(struct rev_info *revs) if (revs->limited) limit_list(revs); if (revs->topo_order) - sort_in_topological_order(&revs->commits, revs->lifo); + sort_in_topological_order_fn(&revs->commits, revs->lifo, + revs->topo_setter, + revs->topo_getter); } static int rewrite_one(struct commit **pp) @@ -711,7 +731,7 @@ struct commit *get_revision(struct rev_info *revs) return NULL; if (revs->no_merges && commit->parents && commit->parents->next) goto next; - if (revs->paths && revs->dense) { + if (revs->prune_fn && revs->dense) { if (!(commit->object.flags & TREECHANGE)) goto next; rewrite_parents(commit); diff --git a/revision.h b/revision.h index 31e8f61567..6c2becad13 100644 --- a/revision.h +++ b/revision.h @@ -7,6 +7,10 @@ #define SHOWN (1u<<3) #define TMP_MARK (1u<<4) /* for isolated cases; clean after use */ +struct rev_info; + +typedef void (prune_fn_t)(struct rev_info *revs, struct commit *commit); + struct rev_info { /* Starting list */ struct commit_list *commits; @@ -14,7 +18,8 @@ struct rev_info { /* Basic information */ const char *prefix; - const char **paths; + void *prune_data; + prune_fn_t *prune_fn; /* Traversal flags */ unsigned int dense:1, @@ -33,9 +38,20 @@ struct rev_info { int max_count; unsigned long max_age; unsigned long min_age; + + topo_sort_set_fn_t topo_setter; + topo_sort_get_fn_t topo_getter; }; +#define REV_TREE_SAME 0 +#define REV_TREE_NEW 1 +#define REV_TREE_DIFFERENT 2 + /* revision.c */ +extern int rev_same_tree_as_empty(struct tree *t1); +extern int rev_compare_tree(struct tree *t1, struct tree *t2); + +extern void init_revisions(struct rev_info *revs); extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); extern void prepare_revision_walk(struct rev_info *revs); extern struct commit *get_revision(struct rev_info *revs); diff --git a/t/annotate-tests.sh b/t/annotate-tests.sh index 9c5a15a15e..114938c3ff 100644 --- a/t/annotate-tests.sh +++ b/t/annotate-tests.sh @@ -94,7 +94,7 @@ test_expect_success \ test_expect_success \ 'merge-setup part 4' \ 'echo "evil merge." >>file && - EDITOR=: git commit -a --amend' + EDITOR=: VISUAL=: git commit -a --amend' test_expect_success \ 'Two lines blamed on A, one on B, two on B1, one on B2, one on A U Thor' \ diff --git a/t/t1200-tutorial.sh b/t/t1200-tutorial.sh index c8a85f92dd..10024133e3 100755 --- a/t/t1200-tutorial.sh +++ b/t/t1200-tutorial.sh @@ -128,7 +128,7 @@ test_expect_success 'git show-branch' 'cmp show-branch.expect show-branch.output git checkout mybranch cat > resolve.expect << EOF -Updating from VARIABLE to VARIABLE. +Updating from VARIABLE to VARIABLE example | 1 + hello | 1 + 2 files changed, 2 insertions(+), 0 deletions(-) |