From 5751f49010ec54164b93529e31165e71f5996856 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sat, 12 May 2007 11:45:53 -0400 Subject: [PATCH 01/37] Move remote parsing into a library file out of builtin-push. The new parser is different from the one in builtin-push in two ways: the default is to use the current branch's remote, if there is one, before "origin"; and config is used in preference to remotes. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- Makefile | 5 +- builtin-push.c | 190 +++++------------------------------------ remote.c | 223 +++++++++++++++++++++++++++++++++++++++++++++++++ remote.h | 18 ++++ 4 files changed, 264 insertions(+), 172 deletions(-) create mode 100644 remote.c create mode 100644 remote.h diff --git a/Makefile b/Makefile index 29243c6e8b..35864ed3c4 100644 --- a/Makefile +++ b/Makefile @@ -296,7 +296,8 @@ LIB_H = \ diff.h object.h pack.h pkt-line.h quote.h refs.h list-objects.h sideband.h \ run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h \ tree-walk.h log-tree.h dir.h path-list.h unpack-trees.h builtin.h \ - utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h mailmap.h + utf8.h reflog-walk.h patch-ids.h attr.h decorate.h progress.h \ + mailmap.h remote.h DIFF_OBJS = \ diff.o diff-lib.o diffcore-break.o diffcore-order.o \ @@ -318,7 +319,7 @@ LIB_OBJS = \ write_or_die.o trace.o list-objects.o grep.o match-trees.o \ alloc.o merge-file.o path-list.o help.o unpack-trees.o $(DIFF_OBJS) \ color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o \ - convert.o attr.o decorate.o progress.o mailmap.o symlinks.o + convert.o attr.o decorate.o progress.o mailmap.o symlinks.o remote.o BUILTIN_OBJS = \ builtin-add.o \ diff --git a/builtin-push.c b/builtin-push.c index cb78401c94..0e602f3bf2 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -5,17 +5,13 @@ #include "refs.h" #include "run-command.h" #include "builtin.h" - -#define MAX_URI (16) +#include "remote.h" static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=] [--repo=all] [-f | --force] [-v] [ ...]"; static int all, tags, force, thin = 1, verbose; static const char *receivepack; -#define BUF_SIZE (2084) -static char buffer[BUF_SIZE]; - static const char **refspec; static int refspec_nr; @@ -137,175 +133,29 @@ static void set_refspecs(const char **refs, int nr) expand_refspecs(); } -static int get_remotes_uri(const char *repo, const char *uri[MAX_URI]) -{ - int n = 0; - FILE *f = fopen(git_path("remotes/%s", repo), "r"); - int has_explicit_refspec = refspec_nr || all || tags; - - if (!f) - return -1; - while (fgets(buffer, BUF_SIZE, f)) { - int is_refspec; - char *s, *p; - - if (!prefixcmp(buffer, "URL:")) { - is_refspec = 0; - s = buffer + 4; - } else if (!prefixcmp(buffer, "Push:")) { - is_refspec = 1; - s = buffer + 5; - } else - continue; - - /* Remove whitespace at the head.. */ - while (isspace(*s)) - s++; - if (!*s) - continue; - - /* ..and at the end */ - p = s + strlen(s); - while (isspace(p[-1])) - *--p = 0; - - if (!is_refspec) { - if (n < MAX_URI) - uri[n++] = xstrdup(s); - else - error("more than %d URL's specified, ignoring the rest", MAX_URI); - } - else if (is_refspec && !has_explicit_refspec) { - if (!wildcard_ref(s)) - add_refspec(xstrdup(s)); - } - } - fclose(f); - if (!n) - die("remote '%s' has no URL", repo); - return n; -} - -static const char **config_uri; -static const char *config_repo; -static int config_repo_len; -static int config_current_uri; -static int config_get_refspecs; -static int config_get_receivepack; - -static int get_remote_config(const char* key, const char* value) -{ - if (!prefixcmp(key, "remote.") && - !strncmp(key + 7, config_repo, config_repo_len)) { - if (!strcmp(key + 7 + config_repo_len, ".url")) { - if (config_current_uri < MAX_URI) - config_uri[config_current_uri++] = xstrdup(value); - else - error("more than %d URL's specified, ignoring the rest", MAX_URI); - } - else if (config_get_refspecs && - !strcmp(key + 7 + config_repo_len, ".push")) { - if (!wildcard_ref(value)) - add_refspec(xstrdup(value)); - } - else if (config_get_receivepack && - !strcmp(key + 7 + config_repo_len, ".receivepack")) { - if (!receivepack) { - char *rp = xmalloc(strlen(value) + 16); - sprintf(rp, "--receive-pack=%s", value); - receivepack = rp; - } else - error("more than one receivepack given, using the first"); - } - } - return 0; -} - -static int get_config_remotes_uri(const char *repo, const char *uri[MAX_URI]) -{ - config_repo_len = strlen(repo); - config_repo = repo; - config_current_uri = 0; - config_uri = uri; - config_get_refspecs = !(refspec_nr || all || tags); - config_get_receivepack = (receivepack == NULL); - - git_config(get_remote_config); - return config_current_uri; -} - -static int get_branches_uri(const char *repo, const char *uri[MAX_URI]) -{ - const char *slash = strchr(repo, '/'); - int n = slash ? slash - repo : 1000; - FILE *f = fopen(git_path("branches/%.*s", n, repo), "r"); - char *s, *p; - int len; - - if (!f) - return 0; - s = fgets(buffer, BUF_SIZE, f); - fclose(f); - if (!s) - return 0; - while (isspace(*s)) - s++; - if (!*s) - return 0; - p = s + strlen(s); - while (isspace(p[-1])) - *--p = 0; - len = p - s; - if (slash) - len += strlen(slash); - p = xmalloc(len + 1); - strcpy(p, s); - if (slash) - strcat(p, slash); - uri[0] = p; - return 1; -} - -/* - * Read remotes and branches file, fill the push target URI - * list. If there is no command line refspecs, read Push: lines - * to set up the *refspec list as well. - * return the number of push target URIs - */ -static int read_config(const char *repo, const char *uri[MAX_URI]) -{ - int n; - - if (*repo != '/') { - n = get_remotes_uri(repo, uri); - if (n > 0) - return n; - - n = get_config_remotes_uri(repo, uri); - if (n > 0) - return n; - - n = get_branches_uri(repo, uri); - if (n > 0) - return n; - } - - uri[0] = repo; - return 1; -} - static int do_push(const char *repo) { - const char *uri[MAX_URI]; - int i, n, errs; + int i, errs; int common_argc; const char **argv; int argc; + struct remote *remote = remote_get(repo); - n = read_config(repo, uri); - if (n <= 0) + if (!remote) die("bad repository '%s'", repo); + if (remote->receivepack) { + char *rp = xmalloc(strlen(remote->receivepack) + 16); + sprintf(rp, "--receive-pack=%s", remote->receivepack); + receivepack = rp; + } + if (!refspec && !all && !tags && remote->push_refspec_nr) { + for (i = 0; i < remote->push_refspec_nr; i++) { + if (!wildcard_ref(remote->push_refspec[i])) + add_refspec(remote->push_refspec[i]); + } + } + argv = xmalloc((refspec_nr + 10) * sizeof(char *)); argv[0] = "dummy-send-pack"; argc = 1; @@ -318,12 +168,12 @@ static int do_push(const char *repo) common_argc = argc; errs = 0; - for (i = 0; i < n; i++) { + for (i = 0; i < remote->uri_nr; i++) { int err; int dest_argc = common_argc; int dest_refspec_nr = refspec_nr; const char **dest_refspec = refspec; - const char *dest = uri[i]; + const char *dest = remote->uri[i]; const char *sender = "send-pack"; if (!prefixcmp(dest, "http://") || !prefixcmp(dest, "https://")) @@ -341,7 +191,7 @@ static int do_push(const char *repo) if (!err) continue; - error("failed to push to '%s'", uri[i]); + error("failed to push to '%s'", remote->uri[i]); switch (err) { case -ERR_RUN_COMMAND_FORK: error("unable to fork for %s", sender); @@ -362,7 +212,7 @@ static int do_push(const char *repo) int cmd_push(int argc, const char **argv, const char *prefix) { int i; - const char *repo = "origin"; /* default repository */ + const char *repo = NULL; /* default repository */ for (i = 1; i < argc; i++) { const char *arg = argv[i]; diff --git a/remote.c b/remote.c new file mode 100644 index 0000000000..b032e81ac8 --- /dev/null +++ b/remote.c @@ -0,0 +1,223 @@ +#include "cache.h" +#include "remote.h" +#include "refs.h" + +static struct remote **remotes; +static int allocated_remotes; + +#define BUF_SIZE (2048) +static char buffer[BUF_SIZE]; + +static void add_push_refspec(struct remote *remote, const char *ref) +{ + int nr = remote->push_refspec_nr + 1; + remote->push_refspec = + xrealloc(remote->push_refspec, nr * sizeof(char *)); + remote->push_refspec[nr-1] = ref; + remote->push_refspec_nr = nr; +} + +static void add_uri(struct remote *remote, const char *uri) +{ + int nr = remote->uri_nr + 1; + remote->uri = + xrealloc(remote->uri, nr * sizeof(char *)); + remote->uri[nr-1] = uri; + remote->uri_nr = nr; +} + +static struct remote *make_remote(const char *name, int len) +{ + int i, empty = -1; + + for (i = 0; i < allocated_remotes; i++) { + if (!remotes[i]) { + if (empty < 0) + empty = i; + } else { + if (len ? (!strncmp(name, remotes[i]->name, len) && + !remotes[i]->name[len]) : + !strcmp(name, remotes[i]->name)) + return remotes[i]; + } + } + + if (empty < 0) { + empty = allocated_remotes; + allocated_remotes += allocated_remotes ? allocated_remotes : 1; + remotes = xrealloc(remotes, + sizeof(*remotes) * allocated_remotes); + memset(remotes + empty, 0, + (allocated_remotes - empty) * sizeof(*remotes)); + } + remotes[empty] = xcalloc(1, sizeof(struct remote)); + if (len) + remotes[empty]->name = xstrndup(name, len); + else + remotes[empty]->name = xstrdup(name); + return remotes[empty]; +} + +static void read_remotes_file(struct remote *remote) +{ + FILE *f = fopen(git_path("remotes/%s", remote->name), "r"); + + if (!f) + return; + while (fgets(buffer, BUF_SIZE, f)) { + int value_list; + char *s, *p; + + if (!prefixcmp(buffer, "URL:")) { + value_list = 0; + s = buffer + 4; + } else if (!prefixcmp(buffer, "Push:")) { + value_list = 1; + s = buffer + 5; + } else + continue; + + while (isspace(*s)) + s++; + if (!*s) + continue; + + p = s + strlen(s); + while (isspace(p[-1])) + *--p = 0; + + switch (value_list) { + case 0: + add_uri(remote, xstrdup(s)); + break; + case 1: + add_push_refspec(remote, xstrdup(s)); + break; + } + } + fclose(f); +} + +static void read_branches_file(struct remote *remote) +{ + const char *slash = strchr(remote->name, '/'); + int n = slash ? slash - remote->name : 1000; + FILE *f = fopen(git_path("branches/%.*s", n, remote->name), "r"); + char *s, *p; + int len; + + if (!f) + return; + s = fgets(buffer, BUF_SIZE, f); + fclose(f); + if (!s) + return; + while (isspace(*s)) + s++; + if (!*s) + return; + p = s + strlen(s); + while (isspace(p[-1])) + *--p = 0; + len = p - s; + if (slash) + len += strlen(slash); + p = xmalloc(len + 1); + strcpy(p, s); + if (slash) + strcat(p, slash); + add_uri(remote, p); +} + +static char *default_remote_name = NULL; +static const char *current_branch = NULL; +static int current_branch_len = 0; + +static int handle_config(const char *key, const char *value) +{ + const char *name; + const char *subkey; + struct remote *remote; + if (!prefixcmp(key, "branch.") && current_branch && + !strncmp(key + 7, current_branch, current_branch_len) && + !strcmp(key + 7 + current_branch_len, ".remote")) { + free(default_remote_name); + default_remote_name = xstrdup(value); + } + if (prefixcmp(key, "remote.")) + return 0; + name = key + 7; + subkey = strrchr(name, '.'); + if (!subkey) + return error("Config with no key for remote %s", name); + if (*subkey == '/') { + warning("Config remote shorthand cannot begin with '/': %s", name); + return 0; + } + remote = make_remote(name, subkey - name); + if (!value) { + /* if we ever have a boolean variable, e.g. "remote.*.disabled" + * [remote "frotz"] + * disabled + * is a valid way to set it to true; we get NULL in value so + * we need to handle it here. + * + * if (!strcmp(subkey, ".disabled")) { + * val = git_config_bool(key, value); + * return 0; + * } else + * + */ + return 0; /* ignore unknown booleans */ + } + if (!strcmp(subkey, ".url")) { + add_uri(remote, xstrdup(value)); + } else if (!strcmp(subkey, ".push")) { + add_push_refspec(remote, xstrdup(value)); + } else if (!strcmp(subkey, ".receivepack")) { + if (!remote->receivepack) + remote->receivepack = xstrdup(value); + else + error("more than one receivepack given, using the first"); + } + return 0; +} + +static void read_config(void) +{ + unsigned char sha1[20]; + const char *head_ref; + int flag; + if (default_remote_name) // did this already + return; + default_remote_name = xstrdup("origin"); + current_branch = NULL; + head_ref = resolve_ref("HEAD", sha1, 0, &flag); + if (head_ref && (flag & REF_ISSYMREF) && + !prefixcmp(head_ref, "refs/heads/")) { + current_branch = head_ref + strlen("refs/heads/"); + current_branch_len = strlen(current_branch); + } + git_config(handle_config); +} + +struct remote *remote_get(const char *name) +{ + struct remote *ret; + + read_config(); + if (!name) + name = default_remote_name; + ret = make_remote(name, 0); + if (name[0] != '/') { + if (!ret->uri) + read_remotes_file(ret); + if (!ret->uri) + read_branches_file(ret); + } + if (!ret->uri) + add_uri(ret, name); + if (!ret->uri) + return NULL; + return ret; +} diff --git a/remote.h b/remote.h new file mode 100644 index 0000000000..73747a8054 --- /dev/null +++ b/remote.h @@ -0,0 +1,18 @@ +#ifndef REMOTE_H +#define REMOTE_H + +struct remote { + const char *name; + + const char **uri; + int uri_nr; + + const char **push_refspec; + int push_refspec_nr; + + const char *receivepack; +}; + +struct remote *remote_get(const char *name); + +#endif From 6b62816cb12e621c5952f088542bec6dfc7ec5d6 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sat, 12 May 2007 11:45:59 -0400 Subject: [PATCH 02/37] Move refspec parser from connect.c and cache.h to remote.{c,h} Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- cache.h | 2 - connect.c | 240 +------------------------------------------------- http-push.c | 1 + remote.c | 246 ++++++++++++++++++++++++++++++++++++++++++++++++++++ remote.h | 12 +++ send-pack.c | 1 + 6 files changed, 261 insertions(+), 241 deletions(-) diff --git a/cache.h b/cache.h index 65b4685c1f..0e6439b0dd 100644 --- a/cache.h +++ b/cache.h @@ -467,8 +467,6 @@ struct ref { extern pid_t git_connect(int fd[2], char *url, const char *prog, int flags); extern int finish_connect(pid_t pid); extern int path_match(const char *path, int nr, char **match); -extern int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, char **refspec, int all); extern int get_ack(int fd, unsigned char *result_sha1); extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, char **match, unsigned int flags); extern int server_supports(const char *feature); diff --git a/connect.c b/connect.c index 2a26fdbe0d..2f61ea3422 100644 --- a/connect.c +++ b/connect.c @@ -4,6 +4,7 @@ #include "quote.h" #include "refs.h" #include "run-command.h" +#include "remote.h" static char *server_capabilities; @@ -128,245 +129,6 @@ int path_match(const char *path, int nr, char **match) return 0; } -struct refspec { - char *src; - char *dst; - char force; -}; - -/* - * A:B means fast forward remote B with local A. - * +A:B means overwrite remote B with local A. - * +A is a shorthand for +A:A. - * A is a shorthand for A:A. - * :B means delete remote B. - */ -static struct refspec *parse_ref_spec(int nr_refspec, char **refspec) -{ - int i; - struct refspec *rs = xcalloc(sizeof(*rs), (nr_refspec + 1)); - for (i = 0; i < nr_refspec; i++) { - char *sp, *dp, *ep; - sp = refspec[i]; - if (*sp == '+') { - rs[i].force = 1; - sp++; - } - ep = strchr(sp, ':'); - if (ep) { - dp = ep + 1; - *ep = 0; - } - else - dp = sp; - rs[i].src = sp; - rs[i].dst = dp; - } - rs[nr_refspec].src = rs[nr_refspec].dst = NULL; - return rs; -} - -static int count_refspec_match(const char *pattern, - struct ref *refs, - struct ref **matched_ref) -{ - int patlen = strlen(pattern); - struct ref *matched_weak = NULL; - struct ref *matched = NULL; - int weak_match = 0; - int match = 0; - - for (weak_match = match = 0; refs; refs = refs->next) { - char *name = refs->name; - int namelen = strlen(name); - int weak_match; - - if (namelen < patlen || - memcmp(name + namelen - patlen, pattern, patlen)) - continue; - if (namelen != patlen && name[namelen - patlen - 1] != '/') - continue; - - /* A match is "weak" if it is with refs outside - * heads or tags, and did not specify the pattern - * in full (e.g. "refs/remotes/origin/master") or at - * least from the toplevel (e.g. "remotes/origin/master"); - * otherwise "git push $URL master" would result in - * ambiguity between remotes/origin/master and heads/master - * at the remote site. - */ - if (namelen != patlen && - patlen != namelen - 5 && - prefixcmp(name, "refs/heads/") && - prefixcmp(name, "refs/tags/")) { - /* We want to catch the case where only weak - * matches are found and there are multiple - * matches, and where more than one strong - * matches are found, as ambiguous. One - * strong match with zero or more weak matches - * are acceptable as a unique match. - */ - matched_weak = refs; - weak_match++; - } - else { - matched = refs; - match++; - } - } - if (!matched) { - *matched_ref = matched_weak; - return weak_match; - } - else { - *matched_ref = matched; - return match; - } -} - -static void link_dst_tail(struct ref *ref, struct ref ***tail) -{ - **tail = ref; - *tail = &ref->next; - **tail = NULL; -} - -static struct ref *try_explicit_object_name(const char *name) -{ - unsigned char sha1[20]; - struct ref *ref; - int len; - - if (!*name) { - ref = xcalloc(1, sizeof(*ref) + 20); - strcpy(ref->name, "(delete)"); - hashclr(ref->new_sha1); - return ref; - } - if (get_sha1(name, sha1)) - return NULL; - len = strlen(name) + 1; - ref = xcalloc(1, sizeof(*ref) + len); - memcpy(ref->name, name, len); - hashcpy(ref->new_sha1, sha1); - return ref; -} - -static int match_explicit_refs(struct ref *src, struct ref *dst, - struct ref ***dst_tail, struct refspec *rs) -{ - int i, errs; - for (i = errs = 0; rs[i].src; i++) { - struct ref *matched_src, *matched_dst; - - matched_src = matched_dst = NULL; - switch (count_refspec_match(rs[i].src, src, &matched_src)) { - case 1: - break; - case 0: - /* The source could be in the get_sha1() format - * not a reference name. :refs/other is a - * way to delete 'other' ref at the remote end. - */ - matched_src = try_explicit_object_name(rs[i].src); - if (matched_src) - break; - errs = 1; - error("src refspec %s does not match any.", - rs[i].src); - break; - default: - errs = 1; - error("src refspec %s matches more than one.", - rs[i].src); - break; - } - switch (count_refspec_match(rs[i].dst, dst, &matched_dst)) { - case 1: - break; - case 0: - if (!memcmp(rs[i].dst, "refs/", 5)) { - int len = strlen(rs[i].dst) + 1; - matched_dst = xcalloc(1, sizeof(*dst) + len); - memcpy(matched_dst->name, rs[i].dst, len); - link_dst_tail(matched_dst, dst_tail); - } - else if (!strcmp(rs[i].src, rs[i].dst) && - matched_src) { - /* pushing "master:master" when - * remote does not have master yet. - */ - int len = strlen(matched_src->name) + 1; - matched_dst = xcalloc(1, sizeof(*dst) + len); - memcpy(matched_dst->name, matched_src->name, - len); - link_dst_tail(matched_dst, dst_tail); - } - else { - errs = 1; - error("dst refspec %s does not match any " - "existing ref on the remote and does " - "not start with refs/.", rs[i].dst); - } - break; - default: - errs = 1; - error("dst refspec %s matches more than one.", - rs[i].dst); - break; - } - if (errs) - continue; - if (matched_dst->peer_ref) { - errs = 1; - error("dst ref %s receives from more than one src.", - matched_dst->name); - } - else { - matched_dst->peer_ref = matched_src; - matched_dst->force = rs[i].force; - } - } - return -errs; -} - -static struct ref *find_ref_by_name(struct ref *list, const char *name) -{ - for ( ; list; list = list->next) - if (!strcmp(list->name, name)) - return list; - return NULL; -} - -int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, - int nr_refspec, char **refspec, int all) -{ - struct refspec *rs = parse_ref_spec(nr_refspec, refspec); - - if (nr_refspec) - return match_explicit_refs(src, dst, dst_tail, rs); - - /* pick the remainder */ - for ( ; src; src = src->next) { - struct ref *dst_peer; - if (src->peer_ref) - continue; - dst_peer = find_ref_by_name(dst, src->name); - if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all)) - continue; - if (!dst_peer) { - /* Create a new one and link it */ - int len = strlen(src->name) + 1; - dst_peer = xcalloc(1, sizeof(*dst_peer) + len); - memcpy(dst_peer->name, src->name, len); - hashcpy(dst_peer->new_sha1, src->new_sha1); - link_dst_tail(dst_peer, dst_tail); - } - dst_peer->peer_ref = src; - } - return 0; -} - enum protocol { PROTO_LOCAL = 1, PROTO_SSH, diff --git a/http-push.c b/http-push.c index e3f767582b..79d2c38608 100644 --- a/http-push.c +++ b/http-push.c @@ -9,6 +9,7 @@ #include "diff.h" #include "revision.h" #include "exec_cmd.h" +#include "remote.h" #include diff --git a/remote.c b/remote.c index b032e81ac8..bc27a8e3df 100644 --- a/remote.c +++ b/remote.c @@ -201,6 +201,44 @@ static void read_config(void) git_config(handle_config); } +static struct refspec *parse_ref_spec(int nr_refspec, const char **refspec) +{ + int i; + struct refspec *rs = xcalloc(sizeof(*rs), nr_refspec); + for (i = 0; i < nr_refspec; i++) { + const char *sp, *ep, *gp; + sp = refspec[i]; + if (*sp == '+') { + rs[i].force = 1; + sp++; + } + gp = strchr(sp, '*'); + ep = strchr(sp, ':'); + if (gp && ep && gp > ep) + gp = NULL; + if (ep) { + if (ep[1]) { + const char *glob = strchr(ep + 1, '*'); + if (!glob) + gp = NULL; + if (gp) + rs[i].dst = xstrndup(ep + 1, + glob - ep - 1); + else + rs[i].dst = xstrdup(ep + 1); + } + } else { + ep = sp + strlen(sp); + } + if (gp) { + rs[i].pattern = 1; + ep = gp; + } + rs[i].src = xstrndup(sp, ep - sp); + } + return rs; +} + struct remote *remote_get(const char *name) { struct remote *ret; @@ -219,5 +257,213 @@ struct remote *remote_get(const char *name) add_uri(ret, name); if (!ret->uri) return NULL; + ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec); return ret; } + +static int count_refspec_match(const char *pattern, + struct ref *refs, + struct ref **matched_ref) +{ + int patlen = strlen(pattern); + struct ref *matched_weak = NULL; + struct ref *matched = NULL; + int weak_match = 0; + int match = 0; + + for (weak_match = match = 0; refs; refs = refs->next) { + char *name = refs->name; + int namelen = strlen(name); + int weak_match; + + if (namelen < patlen || + memcmp(name + namelen - patlen, pattern, patlen)) + continue; + if (namelen != patlen && name[namelen - patlen - 1] != '/') + continue; + + /* A match is "weak" if it is with refs outside + * heads or tags, and did not specify the pattern + * in full (e.g. "refs/remotes/origin/master") or at + * least from the toplevel (e.g. "remotes/origin/master"); + * otherwise "git push $URL master" would result in + * ambiguity between remotes/origin/master and heads/master + * at the remote site. + */ + if (namelen != patlen && + patlen != namelen - 5 && + prefixcmp(name, "refs/heads/") && + prefixcmp(name, "refs/tags/")) { + /* We want to catch the case where only weak + * matches are found and there are multiple + * matches, and where more than one strong + * matches are found, as ambiguous. One + * strong match with zero or more weak matches + * are acceptable as a unique match. + */ + matched_weak = refs; + weak_match++; + } + else { + matched = refs; + match++; + } + } + if (!matched) { + *matched_ref = matched_weak; + return weak_match; + } + else { + *matched_ref = matched; + return match; + } +} + +static void link_dst_tail(struct ref *ref, struct ref ***tail) +{ + **tail = ref; + *tail = &ref->next; + **tail = NULL; +} + +static struct ref *try_explicit_object_name(const char *name) +{ + unsigned char sha1[20]; + struct ref *ref; + int len; + + if (!*name) { + ref = xcalloc(1, sizeof(*ref) + 20); + strcpy(ref->name, "(delete)"); + hashclr(ref->new_sha1); + return ref; + } + if (get_sha1(name, sha1)) + return NULL; + len = strlen(name) + 1; + ref = xcalloc(1, sizeof(*ref) + len); + memcpy(ref->name, name, len); + hashcpy(ref->new_sha1, sha1); + return ref; +} + +static int match_explicit_refs(struct ref *src, struct ref *dst, + struct ref ***dst_tail, struct refspec *rs, + int rs_nr) +{ + int i, errs; + for (i = errs = 0; i < rs_nr; i++) { + struct ref *matched_src, *matched_dst; + + const char *dst_value = rs[i].dst; + if (dst_value == NULL) + dst_value = rs[i].src; + + matched_src = matched_dst = NULL; + switch (count_refspec_match(rs[i].src, src, &matched_src)) { + case 1: + break; + case 0: + /* The source could be in the get_sha1() format + * not a reference name. :refs/other is a + * way to delete 'other' ref at the remote end. + */ + matched_src = try_explicit_object_name(rs[i].src); + if (matched_src) + break; + errs = 1; + error("src refspec %s does not match any.", + rs[i].src); + break; + default: + errs = 1; + error("src refspec %s matches more than one.", + rs[i].src); + break; + } + switch (count_refspec_match(dst_value, dst, &matched_dst)) { + case 1: + break; + case 0: + if (!memcmp(dst_value, "refs/", 5)) { + int len = strlen(dst_value) + 1; + matched_dst = xcalloc(1, sizeof(*dst) + len); + memcpy(matched_dst->name, dst_value, len); + link_dst_tail(matched_dst, dst_tail); + } + else if (!strcmp(rs[i].src, dst_value) && + matched_src) { + /* pushing "master:master" when + * remote does not have master yet. + */ + int len = strlen(matched_src->name) + 1; + matched_dst = xcalloc(1, sizeof(*dst) + len); + memcpy(matched_dst->name, matched_src->name, + len); + link_dst_tail(matched_dst, dst_tail); + } + else { + errs = 1; + error("dst refspec %s does not match any " + "existing ref on the remote and does " + "not start with refs/.", dst_value); + } + break; + default: + errs = 1; + error("dst refspec %s matches more than one.", + dst_value); + break; + } + if (errs) + continue; + if (matched_dst->peer_ref) { + errs = 1; + error("dst ref %s receives from more than one src.", + matched_dst->name); + } + else { + matched_dst->peer_ref = matched_src; + matched_dst->force = rs[i].force; + } + } + return -errs; +} + +static struct ref *find_ref_by_name(struct ref *list, const char *name) +{ + for ( ; list; list = list->next) + if (!strcmp(list->name, name)) + return list; + return NULL; +} + +int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, + int nr_refspec, char **refspec, int all) +{ + struct refspec *rs = + parse_ref_spec(nr_refspec, (const char **) refspec); + + if (nr_refspec) + return match_explicit_refs(src, dst, dst_tail, rs, nr_refspec); + + /* pick the remainder */ + for ( ; src; src = src->next) { + struct ref *dst_peer; + if (src->peer_ref) + continue; + dst_peer = find_ref_by_name(dst, src->name); + if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all)) + continue; + if (!dst_peer) { + /* Create a new one and link it */ + int len = strlen(src->name) + 1; + dst_peer = xcalloc(1, sizeof(*dst_peer) + len); + memcpy(dst_peer->name, src->name, len); + hashcpy(dst_peer->new_sha1, src->new_sha1); + link_dst_tail(dst_peer, dst_tail); + } + dst_peer->peer_ref = src; + } + return 0; +} diff --git a/remote.h b/remote.h index 73747a8054..3bc035b90b 100644 --- a/remote.h +++ b/remote.h @@ -8,6 +8,7 @@ struct remote { int uri_nr; const char **push_refspec; + struct refspec *push; int push_refspec_nr; const char *receivepack; @@ -15,4 +16,15 @@ struct remote { struct remote *remote_get(const char *name); +struct refspec { + unsigned force : 1; + unsigned pattern : 1; + + const char *src; + char *dst; +}; + +int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, + int nr_refspec, char **refspec, int all); + #endif diff --git a/send-pack.c b/send-pack.c index 83ee87dcf8..3fe696cddc 100644 --- a/send-pack.c +++ b/send-pack.c @@ -4,6 +4,7 @@ #include "refs.h" #include "pkt-line.h" #include "run-command.h" +#include "remote.h" static const char send_pack_usage[] = "git-send-pack [--all] [--force] [--receive-pack=] [--verbose] [--thin] [:] [...]\n" From 5d46c9d41febe5fe85f94f7db2b190d8abf1e71e Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Sat, 12 May 2007 11:46:03 -0400 Subject: [PATCH 03/37] Add handlers for fetch-side configuration of remotes. These follow the pattern of the push side configuration, but aren't taken from anywhere else, because git-fetch is still in shell. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- remote.c | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ remote.h | 11 +++++++++++ 2 files changed, 70 insertions(+) diff --git a/remote.c b/remote.c index bc27a8e3df..46fe8d91b5 100644 --- a/remote.c +++ b/remote.c @@ -17,6 +17,15 @@ static void add_push_refspec(struct remote *remote, const char *ref) remote->push_refspec_nr = nr; } +static void add_fetch_refspec(struct remote *remote, const char *ref) +{ + int nr = remote->fetch_refspec_nr + 1; + remote->fetch_refspec = + xrealloc(remote->fetch_refspec, nr * sizeof(char *)); + remote->fetch_refspec[nr-1] = ref; + remote->fetch_refspec_nr = nr; +} + static void add_uri(struct remote *remote, const char *uri) { int nr = remote->uri_nr + 1; @@ -74,6 +83,9 @@ static void read_remotes_file(struct remote *remote) } else if (!prefixcmp(buffer, "Push:")) { value_list = 1; s = buffer + 5; + } else if (!prefixcmp(buffer, "Pull:")) { + value_list = 2; + s = buffer + 5; } else continue; @@ -93,6 +105,9 @@ static void read_remotes_file(struct remote *remote) case 1: add_push_refspec(remote, xstrdup(s)); break; + case 2: + add_fetch_refspec(remote, xstrdup(s)); + break; } } fclose(f); @@ -174,6 +189,8 @@ static int handle_config(const char *key, const char *value) add_uri(remote, xstrdup(value)); } else if (!strcmp(subkey, ".push")) { add_push_refspec(remote, xstrdup(value)); + } else if (!strcmp(subkey, ".fetch")) { + add_fetch_refspec(remote, xstrdup(value)); } else if (!strcmp(subkey, ".receivepack")) { if (!remote->receivepack) remote->receivepack = xstrdup(value); @@ -257,10 +274,52 @@ struct remote *remote_get(const char *name) add_uri(ret, name); if (!ret->uri) return NULL; + ret->fetch = parse_ref_spec(ret->fetch_refspec_nr, ret->fetch_refspec); ret->push = parse_ref_spec(ret->push_refspec_nr, ret->push_refspec); return ret; } +int remote_has_uri(struct remote *remote, const char *uri) +{ + int i; + for (i = 0; i < remote->uri_nr; i++) { + if (!strcmp(remote->uri[i], uri)) + return 1; + } + return 0; +} + +int remote_find_tracking(struct remote *remote, struct refspec *refspec) +{ + int i; + for (i = 0; i < remote->fetch_refspec_nr; i++) { + struct refspec *fetch = &remote->fetch[i]; + if (!fetch->dst) + continue; + if (fetch->pattern) { + if (!prefixcmp(refspec->src, fetch->src)) { + refspec->dst = + xmalloc(strlen(fetch->dst) + + strlen(refspec->src) - + strlen(fetch->src) + 1); + strcpy(refspec->dst, fetch->dst); + strcpy(refspec->dst + strlen(fetch->dst), + refspec->src + strlen(fetch->src)); + refspec->force = fetch->force; + return 0; + } + } else { + if (!strcmp(refspec->src, fetch->src)) { + refspec->dst = xstrdup(fetch->dst); + refspec->force = fetch->force; + return 0; + } + } + } + refspec->dst = NULL; + return -1; +} + static int count_refspec_match(const char *pattern, struct ref *refs, struct ref **matched_ref) diff --git a/remote.h b/remote.h index 3bc035b90b..01dbcef670 100644 --- a/remote.h +++ b/remote.h @@ -11,11 +11,17 @@ struct remote { struct refspec *push; int push_refspec_nr; + const char **fetch_refspec; + struct refspec *fetch; + int fetch_refspec_nr; + const char *receivepack; }; struct remote *remote_get(const char *name); +int remote_has_uri(struct remote *remote, const char *uri); + struct refspec { unsigned force : 1; unsigned pattern : 1; @@ -27,4 +33,9 @@ struct refspec { int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int nr_refspec, char **refspec, int all); +/* + * For the given remote, reads the refspec's src and sets the other fields. + */ +int remote_find_tracking(struct remote *remote, struct refspec *refspec); + #endif From b516968ff62ec153e008d033c153affd7ba9ddc6 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Tue, 15 May 2007 22:50:19 -0400 Subject: [PATCH 04/37] Update local tracking refs when pushing This also adds a --remote option to send-pack, which specifies the configured remote being used. It is provided automatically by git-push, and must match the url (which is still needed, since there could be multiple urls). Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-push.c | 9 +++++++-- send-pack.c | 55 +++++++++++++++++++++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 11 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index 0e602f3bf2..6084899b2b 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -178,8 +178,13 @@ static int do_push(const char *repo) if (!prefixcmp(dest, "http://") || !prefixcmp(dest, "https://")) sender = "http-push"; - else if (thin) - argv[dest_argc++] = "--thin"; + else { + char *rem = xmalloc(strlen(remote->name) + 10); + sprintf(rem, "--remote=%s", remote->name); + argv[dest_argc++] = rem; + if (thin) + argv[dest_argc++] = "--thin"; + } argv[0] = sender; argv[dest_argc++] = dest; while (dest_refspec_nr--) diff --git a/send-pack.c b/send-pack.c index 3fe696cddc..2c0b19ba34 100644 --- a/send-pack.c +++ b/send-pack.c @@ -177,7 +177,7 @@ static int receive_status(int in) return ret; } -static int send_pack(int in, int out, int nr_refspec, char **refspec) +static int send_pack(int in, int out, struct remote *remote, int nr_refspec, char **refspec) { struct ref *ref; int new_refs; @@ -214,18 +214,19 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) new_refs = 0; for (ref = remote_refs; ref; ref = ref->next) { char old_hex[60], *new_hex; - int delete_ref; + int will_delete_ref; if (!ref->peer_ref) continue; - delete_ref = is_null_sha1(ref->peer_ref->new_sha1); - if (delete_ref && !allow_deleting_refs) { + + will_delete_ref = is_null_sha1(ref->peer_ref->new_sha1); + if (will_delete_ref && !allow_deleting_refs) { error("remote does not support deleting refs"); ret = -2; continue; } - if (!delete_ref && + if (!will_delete_ref && !hashcmp(ref->old_sha1, ref->peer_ref->new_sha1)) { if (verbose) fprintf(stderr, "'%s': up-to-date\n", ref->name); @@ -252,7 +253,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) */ if (!force_update && - !delete_ref && + !will_delete_ref && !is_null_sha1(ref->old_sha1) && !ref->force) { if (!has_sha1_file(ref->old_sha1) || @@ -276,7 +277,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) } } hashcpy(ref->new_sha1, ref->peer_ref->new_sha1); - if (!delete_ref) + if (!will_delete_ref) new_refs++; strcpy(old_hex, sha1_to_hex(ref->old_sha1)); new_hex = sha1_to_hex(ref->new_sha1); @@ -291,7 +292,7 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) else packet_write(out, "%s %s %s", old_hex, new_hex, ref->name); - if (delete_ref) + if (will_delete_ref) fprintf(stderr, "deleting '%s'\n", ref->name); else { fprintf(stderr, "updating '%s'", ref->name); @@ -301,6 +302,28 @@ static int send_pack(int in, int out, int nr_refspec, char **refspec) fprintf(stderr, "\n from %s\n to %s\n", old_hex, new_hex); } + if (remote) { + struct refspec rs; + rs.src = ref->name; + remote_find_tracking(remote, &rs); + if (rs.dst) { + struct ref_lock *lock; + fprintf(stderr, " Also local %s\n", rs.dst); + if (will_delete_ref) { + if (delete_ref(rs.dst, NULL)) { + error("Failed to delete"); + } + } else { + lock = lock_any_ref_for_update(rs.dst, NULL, 0); + if (!lock) + error("Failed to lock"); + else + write_ref_sha1(lock, ref->new_sha1, + "update by push"); + } + free(rs.dst); + } + } } packet_flush(out); @@ -345,6 +368,8 @@ int main(int argc, char **argv) char **heads = NULL; int fd[2], ret; pid_t pid; + char *remote_name = NULL; + struct remote *remote = NULL; setup_git_directory(); git_config(git_default_config); @@ -362,6 +387,10 @@ int main(int argc, char **argv) receivepack = arg + 7; continue; } + if (!prefixcmp(arg, "--remote=")) { + remote_name = arg + 9; + continue; + } if (!strcmp(arg, "--all")) { send_all = 1; continue; @@ -394,10 +423,18 @@ int main(int argc, char **argv) usage(send_pack_usage); verify_remote_names(nr_heads, heads); + if (remote_name) { + remote = remote_get(remote_name); + if (!remote_has_uri(remote, dest)) { + die("Destination %s is not a uri for %s", + dest, remote_name); + } + } + pid = git_connect(fd, dest, receivepack, verbose ? CONNECT_VERBOSE : 0); if (pid < 0) return 1; - ret = send_pack(fd[0], fd[1], nr_heads, heads); + ret = send_pack(fd[0], fd[1], remote, nr_heads, heads); close(fd[0]); close(fd[1]); ret |= finish_connect(pid); From d45cc6e2670bbfecb16c608a2bb0e3f358a9ece7 Mon Sep 17 00:00:00 2001 From: Petr Baudis Date: Sun, 20 May 2007 02:10:13 +0200 Subject: [PATCH 05/37] git-applymbox: Remove command I believe noone uses git-applymbox, and noone definitely should, since it is supposed to be completely superseded and everything by its younger cousin git-am. The only known person in the universe to use it was Linus and he declared some time ago that he will try to use git-am instead in his famous dotest script. The trouble is that git-applymbox existence creates confusing UI. I'm a bit like a recycled newbie to the git porcelain and *I* was confused by git-applymbox primitiveness until I've realized a while later that I'm of course using the wrong command. Signed-off-by: Petr Baudis Signed-off-by: Junio C Hamano --- .gitignore | 1 - Documentation/cmd-list.perl | 1 - Documentation/git-am.txt | 5 +- Documentation/git-applymbox.txt | 98 -------------------------- Documentation/hooks.txt | 6 +- Makefile | 2 +- git-applymbox.sh | 121 -------------------------------- 7 files changed, 6 insertions(+), 228 deletions(-) delete mode 100644 Documentation/git-applymbox.txt delete mode 100755 git-applymbox.sh diff --git a/.gitignore b/.gitignore index 4dc0c395fa..76c0e1b8b9 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ git-add--interactive git-am git-annotate git-apply -git-applymbox git-applypatch git-archimport git-archive diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 443802a9a3..0bca3469e7 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -72,7 +72,6 @@ __DATA__ git-add mainporcelain git-am mainporcelain git-annotate ancillaryinterrogators -git-applymbox ancillaryinterrogators git-applypatch purehelpers git-apply plumbingmanipulators git-archimport foreignscminterface diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 25cf84a0c7..049e46f3f3 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -128,8 +128,7 @@ is terminated before the first occurrence of such a line. When initially invoking it, you give it names of the mailboxes to crunch. Upon seeing the first patch that does not apply, it -aborts in the middle, just like 'git-applymbox' does. You can -recover from this in one of two ways: +aborts in the middle,. You can recover from this in one of two ways: . skip the current patch by re-running the command with '--skip' option. @@ -146,7 +145,7 @@ names. SEE ALSO -------- -gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1]. +gitlink:git-applypatch[1], gitlink:git-apply[1]. Author diff --git a/Documentation/git-applymbox.txt b/Documentation/git-applymbox.txt deleted file mode 100644 index ea919ba5d7..0000000000 --- a/Documentation/git-applymbox.txt +++ /dev/null @@ -1,98 +0,0 @@ -git-applymbox(1) -================ - -NAME ----- -git-applymbox - Apply a series of patches in a mailbox - - -SYNOPSIS --------- -'git-applymbox' [-u] [-k] [-q] [-m] ( -c .dotest/ | ) [ ] - -DESCRIPTION ------------ -Splits mail messages in a mailbox into commit log message, -authorship information and patches, and applies them to the -current branch. - - -OPTIONS -------- --q:: - Apply patches interactively. The user will be given - opportunity to edit the log message and the patch before - attempting to apply it. - --k:: - Usually the program 'cleans up' the Subject: header line - to extract the title line for the commit log message, - among which (1) remove 'Re:' or 're:', (2) leading - whitespaces, (3) '[' up to ']', typically '[PATCH]', and - then prepends "[PATCH] ". This flag forbids this - munging, and is most useful when used to read back 'git - format-patch -k' output. - --m:: - Patches are applied with `git-apply` command, and unless - it cleanly applies without fuzz, the processing fails. - With this flag, if a tree that the patch applies cleanly - is found in a repository, the patch is applied to the - tree and then a 3-way merge between the resulting tree - and the current tree. - --u:: - Pass `-u` flag to `git-mailinfo` (see gitlink:git-mailinfo[1]). - The proposed commit log message taken from the e-mail - are re-coded into UTF-8 encoding (configuration variable - `i18n.commitencoding` can be used to specify project's - preferred encoding if it is not UTF-8). This used to be - optional but now it is the default. -+ -Note that the patch is always used as-is without charset -conversion, even with this flag. - --n:: - Pass `-n` flag to `git-mailinfo` (see - gitlink:git-mailinfo[1]). - --c .dotest/:: - When the patch contained in an e-mail does not cleanly - apply, the command exits with an error message. The - patch and extracted message are found in .dotest/, and - you could re-run 'git applymbox' with '-c .dotest/' - flag to restart the process after inspecting and fixing - them. - -:: - The name of the file that contains the e-mail messages - with patches. This file should be in the UNIX mailbox - format. See 'SubmittingPatches' document to learn about - the formatting convention for e-mail submission. - -:: - The name of the file that contains your "Signed-off-by" - line. See 'SubmittingPatches' document to learn what - "Signed-off-by" line means. You can also just say - 'yes', 'true', 'me', or 'please' to use an automatically - generated "Signed-off-by" line based on your committer - identity. - - -SEE ALSO --------- -gitlink:git-am[1], gitlink:git-applypatch[1]. - - -Author ------- -Written by Linus Torvalds - -Documentation --------------- -Documentation by Junio C Hamano and the git-list . - -GIT ---- -Part of the gitlink:git[7] suite - diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index aabb9750fd..aad17447e8 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -13,7 +13,7 @@ applypatch-msg -------------- This hook is invoked by `git-applypatch` script, which is -typically invoked by `git-applymbox`. It takes a single +typically invoked by `git-am`. It takes a single parameter, the name of the file that holds the proposed commit log message. Exiting with non-zero status causes `git-applypatch` to abort before applying the patch. @@ -30,7 +30,7 @@ pre-applypatch -------------- This hook is invoked by `git-applypatch` script, which is -typically invoked by `git-applymbox`. It takes no parameter, +typically invoked by `git-am`. It takes no parameter, and is invoked after the patch is applied, but before a commit is made. Exiting with non-zero status causes the working tree after application of the patch not committed. @@ -45,7 +45,7 @@ post-applypatch --------------- This hook is invoked by `git-applypatch` script, which is -typically invoked by `git-applymbox`. It takes no parameter, +typically invoked by `git-am`. It takes no parameter, and is invoked after the patch is applied and a commit is made. This hook is meant primarily for notification, and cannot affect diff --git a/Makefile b/Makefile index 29243c6e8b..870179b087 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,7 @@ SCRIPT_SH = \ git-repack.sh git-request-pull.sh git-reset.sh \ git-sh-setup.sh \ git-tag.sh git-verify-tag.sh \ - git-applymbox.sh git-applypatch.sh git-am.sh \ + git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh \ git-lost-found.sh git-quiltimport.sh diff --git a/git-applymbox.sh b/git-applymbox.sh deleted file mode 100755 index c18e80ff8c..0000000000 --- a/git-applymbox.sh +++ /dev/null @@ -1,121 +0,0 @@ -#!/bin/sh -## -## "dotest" is my stupid name for my patch-application script, which -## I never got around to renaming after I tested it. We're now on the -## second generation of scripts, still called "dotest". -## -## Update: Ryan Anderson finally shamed me into naming this "applymbox". -## -## You give it a mbox-format collection of emails, and it will try to -## apply them to the kernel using "applypatch" -## -## The patch application may fail in the middle. In which case: -## (1) look at .dotest/patch and fix it up to apply -## (2) re-run applymbox with -c .dotest/msg-number for the current one. -## Pay a special attention to the commit log message if you do this and -## use a Signoff_file, because applypatch wants to append the sign-off -## message to msg-clean every time it is run. -## -## git-am is supposed to be the newer and better tool for this job. - -USAGE='[-u] [-k] [-q] [-m] (-c .dotest/ | mbox) [signoff]' -. git-sh-setup - -git var GIT_COMMITTER_IDENT >/dev/null || exit - -keep_subject= query_apply= continue= utf8=-u resume=t -while case "$#" in 0) break ;; esac -do - case "$1" in - -u) utf8=-u ;; - -n) utf8=-n ;; - -k) keep_subject=-k ;; - -q) query_apply=t ;; - -c) continue="$2"; resume=f; shift ;; - -m) fall_back_3way=t ;; - -*) usage ;; - *) break ;; - esac - shift -done - -case "$continue" in -'') - rm -rf .dotest - mkdir .dotest - num_msgs=$(git-mailsplit "$1" .dotest) || exit 1 - echo "$num_msgs patch(es) to process." - shift -esac - -files=$(git-diff-index --cached --name-only HEAD) || exit -if [ "$files" ]; then - echo "Dirty index: cannot apply patches (dirty: $files)" >&2 - exit 1 -fi - -case "$query_apply" in -t) touch .dotest/.query_apply -esac -case "$fall_back_3way" in -t) : >.dotest/.3way -esac -case "$keep_subject" in --k) : >.dotest/.keep_subject -esac - -signoff="$1" -set x .dotest/0* -shift -while case "$#" in 0) break;; esac -do - i="$1" - case "$resume,$continue" in - f,$i) resume=t;; - f,*) shift - continue;; - *) - git-mailinfo $keep_subject $utf8 \ - .dotest/msg .dotest/patch <$i >.dotest/info || exit 1 - test -s .dotest/patch || { - echo "Patch is empty. Was it split wrong?" - exit 1 - } - git-stripspace < .dotest/msg > .dotest/msg-clean - ;; - esac - while :; # for fixing up and retry - do - git-applypatch .dotest/msg-clean .dotest/patch .dotest/info "$signoff" - case "$?" in - 0) - # Remove the cleanly applied one to reduce clutter. - rm -f .dotest/$i - ;; - 2) - # 2 is a special exit code from applypatch to indicate that - # the patch wasn't applied, but continue anyway - ;; - *) - ret=$? - if test -f .dotest/.query_apply - then - echo >&2 "* Patch failed." - echo >&2 "* You could fix it up in your editor and" - echo >&2 " retry. If you want to do so, say yes here" - echo >&2 " AFTER fixing .dotest/patch up." - echo >&2 -n "Retry [y/N]? " - read yesno - case "$yesno" in - [Yy]*) - continue ;; - esac - fi - exit $ret - esac - break - done - shift -done -# return to pristine -rm -fr .dotest From 59c8e2cb2aee2e4eb75007602b264bc4e7928bc0 Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Thu, 24 May 2007 19:25:25 -0700 Subject: [PATCH 06/37] Remove git-applypatch The previous one removed git-applymbox, which was the sole user of this tool. Signed-off-by: Junio C Hamano --- .gitignore | 1 - Documentation/SubmittingPatches | 4 +- Documentation/cmd-list.perl | 1 - Documentation/git-am.txt | 2 +- Documentation/git-applypatch.txt | 53 -------- Documentation/git-mailinfo.txt | 2 +- Documentation/hooks.txt | 13 +- Makefile | 2 +- git-applypatch.sh | 212 ------------------------------- 9 files changed, 10 insertions(+), 280 deletions(-) delete mode 100644 Documentation/git-applypatch.txt delete mode 100755 git-applypatch.sh diff --git a/.gitignore b/.gitignore index 76c0e1b8b9..b4e72f5fea 100644 --- a/.gitignore +++ b/.gitignore @@ -7,7 +7,6 @@ git-add--interactive git-am git-annotate git-apply -git-applypatch git-archimport git-archive git-bisect diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index b94d9a8166..b5f2ecd237 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -239,7 +239,7 @@ One test you could do yourself if your MUA is set up correctly is: $ git fetch http://kernel.org/pub/scm/git/git.git master:test-apply $ git checkout test-apply $ git reset --hard - $ git applymbox a.patch + $ git am a.patch If it does not apply correctly, there can be various reasons. @@ -247,7 +247,7 @@ If it does not apply correctly, there can be various reasons. does not have much to do with your MUA. Please rebase the patch appropriately. -* Your MUA corrupted your patch; applymbox would complain that +* Your MUA corrupted your patch; "am" would complain that the patch does not apply. Look at .dotest/ subdirectory and see what 'patch' file contains and check for the common corruption patterns mentioned above. diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 0bca3469e7..645e4372e5 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -72,7 +72,6 @@ __DATA__ git-add mainporcelain git-am mainporcelain git-annotate ancillaryinterrogators -git-applypatch purehelpers git-apply plumbingmanipulators git-archimport foreignscminterface git-archive mainporcelain diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 049e46f3f3..7658fbdaef 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -145,7 +145,7 @@ names. SEE ALSO -------- -gitlink:git-applypatch[1], gitlink:git-apply[1]. +gitlink:git-apply[1]. Author diff --git a/Documentation/git-applypatch.txt b/Documentation/git-applypatch.txt deleted file mode 100644 index 451434a757..0000000000 --- a/Documentation/git-applypatch.txt +++ /dev/null @@ -1,53 +0,0 @@ -git-applypatch(1) -================= - -NAME ----- -git-applypatch - Apply one patch extracted from an e-mail - - -SYNOPSIS --------- -'git-applypatch' [] - -DESCRIPTION ------------ -This is usually not what an end user wants to run directly. See -gitlink:git-am[1] instead. - -Takes three files , , and prepared from an -e-mail message by 'git-mailinfo', and creates a commit. It is -usually not necessary to use this command directly. - -This command can run `applypatch-msg`, `pre-applypatch`, and -`post-applypatch` hooks. See link:hooks.html[hooks] for more -information. - - -OPTIONS -------- -:: - Commit log message (sans the first line, which comes - from e-mail Subject stored in ). - -:: - The patch to apply. - -:: - Author and subject information extracted from e-mail, - used on "author" line and as the first line of the - commit log message. - - -Author ------- -Written by Linus Torvalds - -Documentation --------------- -Documentation by Junio C Hamano and the git-list . - -GIT ---- -Part of the gitlink:git[7] suite - diff --git a/Documentation/git-mailinfo.txt b/Documentation/git-mailinfo.txt index 8eadcebfcf..16956951dd 100644 --- a/Documentation/git-mailinfo.txt +++ b/Documentation/git-mailinfo.txt @@ -16,7 +16,7 @@ DESCRIPTION Reading a single e-mail message from the standard input, and writes the commit log message in file, and the patches in file. The author name, e-mail and e-mail subject are -written out to the standard output to be used by git-applypatch +written out to the standard output to be used by git-am to create a commit. It is usually not necessary to use this command directly. See gitlink:git-am[1] instead. diff --git a/Documentation/hooks.txt b/Documentation/hooks.txt index aad17447e8..6836477ca8 100644 --- a/Documentation/hooks.txt +++ b/Documentation/hooks.txt @@ -12,11 +12,10 @@ This document describes the currently defined hooks. applypatch-msg -------------- -This hook is invoked by `git-applypatch` script, which is -typically invoked by `git-am`. It takes a single +This hook is invoked by `git-am` script. It takes a single parameter, the name of the file that holds the proposed commit log message. Exiting with non-zero status causes -`git-applypatch` to abort before applying the patch. +`git-am` to abort before applying the patch. The hook is allowed to edit the message file in place, and can be used to normalize the message into some project standard @@ -29,8 +28,7 @@ The default 'applypatch-msg' hook, when enabled, runs the pre-applypatch -------------- -This hook is invoked by `git-applypatch` script, which is -typically invoked by `git-am`. It takes no parameter, +This hook is invoked by `git-am`. It takes no parameter, and is invoked after the patch is applied, but before a commit is made. Exiting with non-zero status causes the working tree after application of the patch not committed. @@ -44,12 +42,11 @@ The default 'pre-applypatch' hook, when enabled, runs the post-applypatch --------------- -This hook is invoked by `git-applypatch` script, which is -typically invoked by `git-am`. It takes no parameter, +This hook is invoked by `git-am`. It takes no parameter, and is invoked after the patch is applied and a commit is made. This hook is meant primarily for notification, and cannot affect -the outcome of `git-applypatch`. +the outcome of `git-am`. pre-commit ---------- diff --git a/Makefile b/Makefile index 870179b087..4e63a695f1 100644 --- a/Makefile +++ b/Makefile @@ -206,7 +206,7 @@ SCRIPT_SH = \ git-repack.sh git-request-pull.sh git-reset.sh \ git-sh-setup.sh \ git-tag.sh git-verify-tag.sh \ - git-applypatch.sh git-am.sh \ + git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh \ git-lost-found.sh git-quiltimport.sh diff --git a/git-applypatch.sh b/git-applypatch.sh deleted file mode 100755 index 8df2aee4c2..0000000000 --- a/git-applypatch.sh +++ /dev/null @@ -1,212 +0,0 @@ -#!/bin/sh -## -## applypatch takes four file arguments, and uses those to -## apply the unpacked patch (surprise surprise) that they -## represent to the current tree. -## -## The arguments are: -## $1 - file with commit message -## $2 - file with the actual patch -## $3 - "info" file with Author, email and subject -## $4 - optional file containing signoff to add -## - -USAGE=' []' -. git-sh-setup - -case "$#" in 3|4) ;; *) usage ;; esac - -final=.dotest/final-commit -## -## If this file exists, we ask before applying -## -query_apply=.dotest/.query_apply - -## We do not munge the first line of the commit message too much -## if this file exists. -keep_subject=.dotest/.keep_subject - -## We do not attempt the 3-way merge fallback unless this file exists. -fall_back_3way=.dotest/.3way - -MSGFILE=$1 -PATCHFILE=$2 -INFO=$3 -SIGNOFF=$4 -EDIT=${VISUAL:-${EDITOR:-vi}} - -export GIT_AUTHOR_NAME="$(sed -n '/^Author/ s/Author: //p' "$INFO")" -export GIT_AUTHOR_EMAIL="$(sed -n '/^Email/ s/Email: //p' "$INFO")" -export GIT_AUTHOR_DATE="$(sed -n '/^Date/ s/Date: //p' "$INFO")" -export SUBJECT="$(sed -n '/^Subject/ s/Subject: //p' "$INFO")" - -if test '' != "$SIGNOFF" -then - if test -f "$SIGNOFF" - then - SIGNOFF=`cat "$SIGNOFF"` || exit - elif case "$SIGNOFF" in yes | true | me | please) : ;; *) false ;; esac - then - SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e ' - s/>.*/>/ - s/^/Signed-off-by: /' - ` - else - SIGNOFF= - fi - if test '' != "$SIGNOFF" - then - LAST_SIGNED_OFF_BY=` - sed -ne '/^Signed-off-by: /p' "$MSGFILE" | - tail -n 1 - ` - test "$LAST_SIGNED_OFF_BY" = "$SIGNOFF" || { - test '' = "$LAST_SIGNED_OFF_BY" && echo - echo "$SIGNOFF" - } >>"$MSGFILE" - fi -fi - -patch_header= -test -f "$keep_subject" || patch_header='[PATCH] ' - -{ - echo "$patch_header$SUBJECT" - if test -s "$MSGFILE" - then - echo - cat "$MSGFILE" - fi -} >"$final" - -interactive=yes -test -f "$query_apply" || interactive=no - -while [ "$interactive" = yes ]; do - echo "Commit Body is:" - echo "--------------------------" - cat "$final" - echo "--------------------------" - printf "Apply? [y]es/[n]o/[e]dit/[a]ccept all " - read reply - case "$reply" in - y|Y) interactive=no;; - n|N) exit 2;; # special value to tell dotest to keep going - e|E) "$EDIT" "$final";; - a|A) rm -f "$query_apply" - interactive=no ;; - esac -done - -if test -x "$GIT_DIR"/hooks/applypatch-msg -then - "$GIT_DIR"/hooks/applypatch-msg "$final" || exit -fi - -echo -echo Applying "'$SUBJECT'" -echo - -git-apply --index "$PATCHFILE" || { - - # git-apply exits with status 1 when the patch does not apply, - # but it die()s with other failures, most notably upon corrupt - # patch. In the latter case, there is no point to try applying - # it to another tree and do 3-way merge. - test $? = 1 || exit 1 - - test -f "$fall_back_3way" || exit 1 - - # Here if we know which revision the patch applies to, - # we create a temporary working tree and index, apply the - # patch, and attempt 3-way merge with the resulting tree. - - O_OBJECT=`cd "$GIT_OBJECT_DIRECTORY" && pwd` - rm -fr .patch-merge-* - - if git-apply -z --index-info "$PATCHFILE" \ - >.patch-merge-index-info 2>/dev/null && - GIT_INDEX_FILE=.patch-merge-tmp-index \ - git-update-index -z --index-info <.patch-merge-index-info && - GIT_INDEX_FILE=.patch-merge-tmp-index \ - git-write-tree >.patch-merge-tmp-base && - ( - mkdir .patch-merge-tmp-dir && - cd .patch-merge-tmp-dir && - GIT_INDEX_FILE="../.patch-merge-tmp-index" \ - GIT_OBJECT_DIRECTORY="$O_OBJECT" \ - git-apply $binary --index - ) <"$PATCHFILE" - then - echo Using index info to reconstruct a base tree... - mv .patch-merge-tmp-base .patch-merge-base - mv .patch-merge-tmp-index .patch-merge-index - else - ( - N=10 - - # Otherwise, try nearby trees that can be used to apply the - # patch. - git-rev-list --max-count=$N HEAD - - # or hoping the patch is against known tags... - git-ls-remote --tags . - ) | - while read base junk - do - # Try it if we have it as a tree. - git-cat-file tree "$base" >/dev/null 2>&1 || continue - - rm -fr .patch-merge-tmp-* && - mkdir .patch-merge-tmp-dir || break - ( - cd .patch-merge-tmp-dir && - GIT_INDEX_FILE=../.patch-merge-tmp-index && - GIT_OBJECT_DIRECTORY="$O_OBJECT" && - export GIT_INDEX_FILE GIT_OBJECT_DIRECTORY && - git-read-tree "$base" && - git-apply --index && - mv ../.patch-merge-tmp-index ../.patch-merge-index && - echo "$base" >../.patch-merge-base - ) <"$PATCHFILE" 2>/dev/null && break - done - fi - - test -f .patch-merge-index && - his_tree=$(GIT_INDEX_FILE=.patch-merge-index git-write-tree) && - orig_tree=$(cat .patch-merge-base) && - rm -fr .patch-merge-* || exit 1 - - echo Falling back to patching base and 3-way merge using $orig_tree... - - # This is not so wrong. Depending on which base we picked, - # orig_tree may be wildly different from ours, but his_tree - # has the same set of wildly different changes in parts the - # patch did not touch, so resolve ends up canceling them, - # saying that we reverted all those changes. - - if git-merge-resolve $orig_tree -- HEAD $his_tree - then - echo Done. - else - echo Failed to merge in the changes. - exit 1 - fi -} - -if test -x "$GIT_DIR"/hooks/pre-applypatch -then - "$GIT_DIR"/hooks/pre-applypatch || exit -fi - -tree=$(git-write-tree) || exit 1 -echo Wrote tree $tree -parent=$(git-rev-parse --verify HEAD) && -commit=$(git-commit-tree $tree -p $parent <"$final") || exit 1 -echo Committed: $commit -git-update-ref -m "applypatch: $SUBJECT" HEAD $commit $parent || exit - -if test -x "$GIT_DIR"/hooks/post-applypatch -then - "$GIT_DIR"/hooks/post-applypatch -fi From 8558fd9ece4c8250a037a6d5482a8040d600ef47 Mon Sep 17 00:00:00 2001 From: Daniel Barkalow Date: Fri, 25 May 2007 01:20:56 -0400 Subject: [PATCH 07/37] Move refspec pattern matching to match_refs(). This means that send-pack and http-push will support pattern refspecs, so builtin-push.c doesn't have to expand them, and also git push can just turn --tags into "refs/tags/*", further simplifying builtin-push.c check_ref_format() gets a third "conditionally okay" result for something that's valid as a pattern but not as a particular ref. Signed-off-by: Daniel Barkalow Signed-off-by: Junio C Hamano --- builtin-push.c | 133 ++++++++----------------------------------------- refs.c | 27 +++++++--- remote.c | 31 ++++++++++-- send-pack.c | 1 + 4 files changed, 70 insertions(+), 122 deletions(-) diff --git a/builtin-push.c b/builtin-push.c index 6084899b2b..2612f07f74 100644 --- a/builtin-push.c +++ b/builtin-push.c @@ -9,7 +9,7 @@ static const char push_usage[] = "git-push [--all] [--tags] [--receive-pack=] [--repo=all] [-f | --force] [-v] [ ...]"; -static int all, tags, force, thin = 1, verbose; +static int all, force, thin = 1, verbose; static const char *receivepack; static const char **refspec; @@ -23,114 +23,24 @@ static void add_refspec(const char *ref) refspec_nr = nr; } -static int expand_one_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data) -{ - /* Ignore the "refs/" at the beginning of the refname */ - ref += 5; - - if (!prefixcmp(ref, "tags/")) - add_refspec(xstrdup(ref)); - return 0; -} - -static void expand_refspecs(void) -{ - if (all) { - if (refspec_nr) - die("cannot mix '--all' and a refspec"); - - /* - * No need to expand "--all" - we'll just use - * the "--all" flag to send-pack - */ - return; - } - if (!tags) - return; - for_each_ref(expand_one_ref, NULL); -} - -struct wildcard_cb { - const char *from_prefix; - int from_prefix_len; - const char *to_prefix; - int to_prefix_len; - int force; -}; - -static int expand_wildcard_ref(const char *ref, const unsigned char *sha1, int flag, void *cb_data) -{ - struct wildcard_cb *cb = cb_data; - int len = strlen(ref); - char *expanded, *newref; - - if (len < cb->from_prefix_len || - memcmp(cb->from_prefix, ref, cb->from_prefix_len)) - return 0; - expanded = xmalloc(len * 2 + cb->force + - (cb->to_prefix_len - cb->from_prefix_len) + 2); - newref = expanded + cb->force; - if (cb->force) - expanded[0] = '+'; - memcpy(newref, ref, len); - newref[len] = ':'; - memcpy(newref + len + 1, cb->to_prefix, cb->to_prefix_len); - strcpy(newref + len + 1 + cb->to_prefix_len, - ref + cb->from_prefix_len); - add_refspec(expanded); - return 0; -} - -static int wildcard_ref(const char *ref) -{ - int len; - const char *colon; - struct wildcard_cb cb; - - memset(&cb, 0, sizeof(cb)); - if (ref[0] == '+') { - cb.force = 1; - ref++; - } - len = strlen(ref); - colon = strchr(ref, ':'); - if (! (colon && ref < colon && - colon[-2] == '/' && colon[-1] == '*' && - /* "/:/" is at least 7 bytes */ - 7 <= len && - ref[len-2] == '/' && ref[len-1] == '*') ) - return 0 ; - cb.from_prefix = ref; - cb.from_prefix_len = colon - ref - 1; - cb.to_prefix = colon + 1; - cb.to_prefix_len = len - (colon - ref) - 2; - for_each_ref(expand_wildcard_ref, &cb); - return 1; -} - static void set_refspecs(const char **refs, int nr) { - if (nr) { - int i; - for (i = 0; i < nr; i++) { - const char *ref = refs[i]; - if (!strcmp("tag", ref)) { - char *tag; - int len; - if (nr <= ++i) - die("tag shorthand without "); - len = strlen(refs[i]) + 11; - tag = xmalloc(len); - strcpy(tag, "refs/tags/"); - strcat(tag, refs[i]); - ref = tag; - } - else if (wildcard_ref(ref)) - continue; - add_refspec(ref); + int i; + for (i = 0; i < nr; i++) { + const char *ref = refs[i]; + if (!strcmp("tag", ref)) { + char *tag; + int len; + if (nr <= ++i) + die("tag shorthand without "); + len = strlen(refs[i]) + 11; + tag = xmalloc(len); + strcpy(tag, "refs/tags/"); + strcat(tag, refs[i]); + ref = tag; } + add_refspec(ref); } - expand_refspecs(); } static int do_push(const char *repo) @@ -149,11 +59,9 @@ static int do_push(const char *repo) sprintf(rp, "--receive-pack=%s", remote->receivepack); receivepack = rp; } - if (!refspec && !all && !tags && remote->push_refspec_nr) { - for (i = 0; i < remote->push_refspec_nr; i++) { - if (!wildcard_ref(remote->push_refspec[i])) - add_refspec(remote->push_refspec[i]); - } + if (!refspec && !all && remote->push_refspec_nr) { + refspec = remote->push_refspec; + refspec_nr = remote->push_refspec_nr; } argv = xmalloc((refspec_nr + 10) * sizeof(char *)); @@ -240,7 +148,7 @@ int cmd_push(int argc, const char **argv, const char *prefix) continue; } if (!strcmp(arg, "--tags")) { - tags = 1; + add_refspec("refs/tags/*"); continue; } if (!strcmp(arg, "--force") || !strcmp(arg, "-f")) { @@ -266,5 +174,8 @@ int cmd_push(int argc, const char **argv, const char *prefix) usage(push_usage); } set_refspecs(argv + i, argc - i); + if (all && refspec) + usage(push_usage); + return do_push(repo); } diff --git a/refs.c b/refs.c index 2ae3235b2f..ef4484d293 100644 --- a/refs.c +++ b/refs.c @@ -603,15 +603,20 @@ int get_ref_sha1(const char *ref, unsigned char *sha1) static inline int bad_ref_char(int ch) { - return (((unsigned) ch) <= ' ' || - ch == '~' || ch == '^' || ch == ':' || - /* 2.13 Pattern Matching Notation */ - ch == '?' || ch == '*' || ch == '['); + if (((unsigned) ch) <= ' ' || + ch == '~' || ch == '^' || ch == ':') + return 1; + /* 2.13 Pattern Matching Notation */ + if (ch == '?' || ch == '[') /* Unsupported */ + return 1; + if (ch == '*') /* Supported at the end */ + return 2; + return 0; } int check_ref_format(const char *ref) { - int ch, level; + int ch, level, bad_type; const char *cp = ref; level = 0; @@ -622,13 +627,19 @@ int check_ref_format(const char *ref) return -1; /* should not end with slashes */ /* we are at the beginning of the path component */ - if (ch == '.' || bad_ref_char(ch)) + if (ch == '.') return -1; + bad_type = bad_ref_char(ch); + if (bad_type) { + return (bad_type == 2 && !*cp) ? -3 : -1; + } /* scan the rest of the path component */ while ((ch = *cp++) != 0) { - if (bad_ref_char(ch)) - return -1; + bad_type = bad_ref_char(ch); + if (bad_type) { + return (bad_type == 2 && !*cp) ? -3 : -1; + } if (ch == '/') break; if (ch == '.' && *cp == '.') diff --git a/remote.c b/remote.c index 46fe8d91b5..d904616cdb 100644 --- a/remote.c +++ b/remote.c @@ -415,6 +415,10 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, struct ref *matched_src, *matched_dst; const char *dst_value = rs[i].dst; + + if (rs[i].pattern) + continue; + if (dst_value == NULL) dst_value = rs[i].src; @@ -497,22 +501,43 @@ static struct ref *find_ref_by_name(struct ref *list, const char *name) return NULL; } +static int check_pattern_match(struct refspec *rs, int rs_nr, struct ref *src) +{ + int i; + if (!rs_nr) + return 1; + for (i = 0; i < rs_nr; i++) { + if (rs[i].pattern && !prefixcmp(src->name, rs[i].src)) + return 1; + } + return 0; +} + int match_refs(struct ref *src, struct ref *dst, struct ref ***dst_tail, int nr_refspec, char **refspec, int all) { struct refspec *rs = parse_ref_spec(nr_refspec, (const char **) refspec); - if (nr_refspec) - return match_explicit_refs(src, dst, dst_tail, rs, nr_refspec); + if (match_explicit_refs(src, dst, dst_tail, rs, nr_refspec)) + return -1; /* pick the remainder */ for ( ; src; src = src->next) { struct ref *dst_peer; if (src->peer_ref) continue; + if (!check_pattern_match(rs, nr_refspec, src)) + continue; + dst_peer = find_ref_by_name(dst, src->name); - if ((dst_peer && dst_peer->peer_ref) || (!dst_peer && !all)) + if (dst_peer && dst_peer->peer_ref) + /* We're already sending something to this ref. */ + continue; + if (!dst_peer && !nr_refspec && !all) + /* Remote doesn't have it, and we have no + * explicit pattern, and we don't have + * --all. */ continue; if (!dst_peer) { /* Create a new one and link it */ diff --git a/send-pack.c b/send-pack.c index 2c0b19ba34..fecbda981b 100644 --- a/send-pack.c +++ b/send-pack.c @@ -354,6 +354,7 @@ static void verify_remote_names(int nr_heads, char **heads) case -2: /* ok but a single level -- that is fine for * a match pattern. */ + case -3: /* ok but ends with a pattern-match character */ continue; } die("remote part of refspec is not a valid name in %s", From 70c7ac22de681a83621bda03e676348170c8d8a2 Mon Sep 17 00:00:00 2001 From: Lars Hjemli Date: Sat, 26 May 2007 15:56:40 +0200 Subject: [PATCH 08/37] Add git-submodule command This command can be used to initialize, update and inspect submodules. It uses a .gitmodules file, readable by git-config, in the top level directory of the 'superproject' to specify a mapping between submodule paths and repository url. Example .gitmodules layout: [module "git"] url = git://git.kernel.org/pub/scm/git/git.git With this entry in .gitmodules (and a commit reference in the index entry for the path "git"), the command 'git submodule init' will clone the repository at kernel.org into the directory "git". Known issues ============ There is currently no way to override the url found in the .gitmodules file, except by manually creating the subproject repository. The place to fix this in the script has a rather long comment about a possible plan. Funny paths will be quoted in the output from git-ls-files, but git-submodule does not attempt to unquote (or even detect the presence of) such paths. Signed-off-by: Lars Hjemli Signed-off-by: Junio C Hamano --- .gitignore | 1 + Documentation/cmd-list.perl | 1 + Documentation/git-submodule.txt | 65 +++++++++++ Makefile | 2 +- git-submodule.sh | 194 ++++++++++++++++++++++++++++++++ 5 files changed, 262 insertions(+), 1 deletion(-) create mode 100644 Documentation/git-submodule.txt create mode 100755 git-submodule.sh diff --git a/.gitignore b/.gitignore index 4dc0c395fa..8fc4923792 100644 --- a/.gitignore +++ b/.gitignore @@ -126,6 +126,7 @@ git-ssh-push git-ssh-upload git-status git-stripspace +git-submodule git-svn git-svnimport git-symbolic-ref diff --git a/Documentation/cmd-list.perl b/Documentation/cmd-list.perl index 443802a9a3..51dc6b4105 100755 --- a/Documentation/cmd-list.perl +++ b/Documentation/cmd-list.perl @@ -180,6 +180,7 @@ git-ssh-fetch synchingrepositories git-ssh-upload synchingrepositories git-status mainporcelain git-stripspace purehelpers +git-submodule mainporcelain git-svn foreignscminterface git-svnimport foreignscminterface git-symbolic-ref plumbingmanipulators diff --git a/Documentation/git-submodule.txt b/Documentation/git-submodule.txt new file mode 100644 index 0000000000..cb0424f77b --- /dev/null +++ b/Documentation/git-submodule.txt @@ -0,0 +1,65 @@ +git-submodule(1) +================ + +NAME +---- +git-submodule - Initialize, update or inspect submodules + + +SYNOPSIS +-------- +'git-submodule' [--quiet] [--cached] [status|init|update] [--] [...] + + +COMMANDS +-------- +status:: + Show the status of the submodules. This will print the SHA-1 of the + currently checked out commit for each submodule, along with the + submodule path and the output of gitlink:git-describe[1] for the + SHA-1. Each SHA-1 will be prefixed with `-` if the submodule is not + initialized and `+` if the currently checked out submodule commit + does not match the SHA-1 found in the index of the containing + repository. This command is the default command for git-submodule. + +init:: + Initialize the submodules, i.e. clone the git repositories specified + in the .gitmodules file and checkout the submodule commits specified + in the index of the containing repository. This will make the + submodules HEAD be detached. + +update:: + Update the initialized submodules, i.e. checkout the submodule commits + specified in the index of the containing repository. This will make + the submodules HEAD be detached. + + +OPTIONS +------- +-q, --quiet:: + Only print error messages. + +--cached:: + Display the SHA-1 stored in the index, not the SHA-1 of the currently + checked out submodule commit. This option is only valid for the + status command. + +:: + Path to submodule(s). When specified this will restrict the command + to only operate on the submodules found at the specified paths. + +FILES +----- +When cloning submodules, a .gitmodules file in the top-level directory +of the containing repository is used to find the url of each submodule. +This file should be formatted in the same way as $GIR_DIR/config. The key +to each submodule url is "module.$path.url". + + +AUTHOR +------ +Written by Lars Hjemli + +GIT +--- +Part of the gitlink:git[7] suite diff --git a/Makefile b/Makefile index 29243c6e8b..5cf2169ef0 100644 --- a/Makefile +++ b/Makefile @@ -209,7 +209,7 @@ SCRIPT_SH = \ git-applymbox.sh git-applypatch.sh git-am.sh \ git-merge.sh git-merge-stupid.sh git-merge-octopus.sh \ git-merge-resolve.sh git-merge-ours.sh \ - git-lost-found.sh git-quiltimport.sh + git-lost-found.sh git-quiltimport.sh git-submodule.sh SCRIPT_PERL = \ git-add--interactive.perl \ diff --git a/git-submodule.sh b/git-submodule.sh new file mode 100755 index 0000000000..6ed5a6ced2 --- /dev/null +++ b/git-submodule.sh @@ -0,0 +1,194 @@ +#!/bin/sh +# +# git-submodules.sh: init, update or list git submodules +# +# Copyright (c) 2007 Lars Hjemli + +USAGE='[--quiet] [--cached] [status|init|update] [--] [...]' +. git-sh-setup +require_work_tree + +init= +update= +status= +quiet= +cached= + +# +# print stuff on stdout unless -q was specified +# +say() +{ + if test -z "$quiet" + then + echo "$@" + fi +} + +# +# Run clone + checkout on missing submodules +# +# $@ = requested paths (default to all) +# +modules_init() +{ + git ls-files --stage -- "$@" | grep -e '^160000 ' | + while read mode sha1 stage path + do + # Skip submodule paths that already contain a .git directory. + # This will also trigger if $path is a symlink to a git + # repository + test -d "$path"/.git && continue + + # If there already is a directory at the submodule path, + # expect it to be empty (since that is the default checkout + # action) and try to remove it. + # Note: if $path is a symlink to a directory the test will + # succeed but the rmdir will fail. We might want to fix this. + if test -d "$path" + then + rmdir "$path" 2>/dev/null || + die "Directory '$path' exist, but is neither empty nor a git repository" + fi + + test -e "$path" && + die "A file already exist at path '$path'" + + url=$(GIT_CONFIG=.gitmodules git-config module."$path".url) + test -z "$url" && + die "No url found for submodule '$path' in .gitmodules" + + # MAYBE FIXME: this would be the place to check GIT_CONFIG + # for a preferred url for this submodule, possibly like this: + # + # modname=$(GIT_CONFIG=.gitmodules git-config module."$path".name) + # alturl=$(git-config module."$modname".url) + # + # This would let the versioned .gitmodules file use the submodule + # path as key, while the unversioned GIT_CONFIG would use the + # logical modulename (if present) as key. But this would need + # another fallback mechanism if the module wasn't named. + + git-clone -n "$url" "$path" || + die "Clone of submodule '$path' failed" + + (unset GIT_DIR && cd "$path" && git-checkout -q "$sha1") || + die "Checkout of submodule '$path' failed" + + say "Submodule '$path' initialized" + done +} + +# +# Checkout correct revision of each initialized submodule +# +# $@ = requested paths (default to all) +# +modules_update() +{ + git ls-files --stage -- "$@" | grep -e '^160000 ' | + while read mode sha1 stage path + do + if ! test -d "$path"/.git + then + # Only mention uninitialized submodules when its + # path have been specified + test "$#" != "0" && + say "Submodule '$path' not initialized" + continue; + fi + subsha1=$(unset GIT_DIR && cd "$path" && + git-rev-parse --verify HEAD) || + die "Unable to find current revision of submodule '$path'" + + if test "$subsha1" != "$sha1" + then + (unset GIT_DIR && cd "$path" && git-fetch && + git-checkout -q "$sha1") || + die "Unable to checkout '$sha1' in submodule '$path'" + + say "Submodule '$path': checked out '$sha1'" + fi + done +} + +# +# List all registered submodules, prefixed with: +# - submodule not initialized +# + different revision checked out +# +# If --cached was specified the revision in the index will be printed +# instead of the currently checked out revision. +# +# $@ = requested paths (default to all) +# +modules_list() +{ + git ls-files --stage -- "$@" | grep -e '^160000 ' | + while read mode sha1 stage path + do + if ! test -d "$path"/.git + then + say "-$sha1 $path" + continue; + fi + revname=$(unset GIT_DIR && cd "$path" && git-describe $sha1) + if git diff-files --quiet -- "$path" + then + say " $sha1 $path ($revname)" + else + if test -z "$cached" + then + sha1=$(unset GIT_DIR && cd "$path" && git-rev-parse --verify HEAD) + revname=$(unset GIT_DIR && cd "$path" && git-describe $sha1) + fi + say "+$sha1 $path ($revname)" + fi + done +} + +while case "$#" in 0) break ;; esac +do + case "$1" in + init) + init=1 + ;; + update) + update=1 + ;; + status) + status=1 + ;; + -q|--quiet) + quiet=1 + ;; + --cached) + cached=1 + ;; + --) + break + ;; + -*) + usage + ;; + *) + break + ;; + esac + shift +done + +case "$init,$update,$status,$cached" in +1,,,) + modules_init "$@" + ;; +,1,,) + modules_update "$@" + ;; +,,*,*) + modules_list "$@" + ;; +*) + usage + ;; +esac From d079837eeeadc37d266113a1fd2deb0a01aaee91 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 May 2007 01:24:19 -0400 Subject: [PATCH 09/37] Lazily open pack index files on demand In some repository configurations the user may have many packfiles, but all of the recent commits/trees/tags/blobs are likely to be in the most recent packfile (the one with the newest mtime). It is therefore common to be able to complete an entire operation by accessing only one packfile, even if there are 25 packfiles available to the repository. Rather than opening and mmaping the corresponding .idx file for every pack found, we now only open and map the .idx when we suspect there might be an object of interest in there. Of course we cannot known in advance which packfile contains an object, so we still need to scan the entire packed_git list to locate anything. But odds are users want to access objects in the most recently created packfiles first, and that may be all they ever need for the current operation. Junio observed in b867092f that placing recent packfiles before older ones can slightly improve access times for recent objects, without degrading it for historical object access. This change improves upon Junio's observations by trying even harder to avoid the .idx files that we won't need. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-count-objects.c | 2 ++ cache.h | 3 ++- pack-check.c | 9 +++++++-- pack-redundant.c | 3 +++ sha1_file.c | 38 +++++++++++++++++++++++++++++++++++--- 5 files changed, 49 insertions(+), 6 deletions(-) diff --git a/builtin-count-objects.c b/builtin-count-objects.c index ff90ebd465..ac65e03e7f 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -111,6 +111,8 @@ int cmd_count_objects(int ac, const char **av, const char *prefix) for (p = packed_git; p; p = p->next) { if (!p->pack_local) continue; + if (!p->index_data && open_pack_index(p)) + continue; packed += p->num_objects; num_pack++; } diff --git a/cache.h b/cache.h index cd875bc2e9..0f4a05b51e 100644 --- a/cache.h +++ b/cache.h @@ -485,10 +485,11 @@ extern struct packed_git *find_sha1_pack(const unsigned char *sha1, struct packed_git *packs); extern void pack_report(void); +extern int open_pack_index(struct packed_git *); extern unsigned char* use_pack(struct packed_git *, struct pack_window **, off_t, unsigned int *); extern void unuse_pack(struct pack_window **); extern struct packed_git *add_packed_git(const char *, int, int); -extern const unsigned char *nth_packed_object_sha1(const struct packed_git *, uint32_t); +extern const unsigned char *nth_packed_object_sha1(struct packed_git *, uint32_t); extern off_t find_pack_entry_one(const unsigned char *, struct packed_git *); extern void *unpack_entry(struct packed_git *, off_t, enum object_type *, unsigned long *); extern unsigned long unpack_object_header_gently(const unsigned char *buf, unsigned long len, enum object_type *type, unsigned long *sizep); diff --git a/pack-check.c b/pack-check.c index c168642c0c..3623c716e3 100644 --- a/pack-check.c +++ b/pack-check.c @@ -128,12 +128,17 @@ static void show_pack_info(struct packed_git *p) int verify_pack(struct packed_git *p, int verbose) { - off_t index_size = p->index_size; - const unsigned char *index_base = p->index_data; + off_t index_size; + const unsigned char *index_base; SHA_CTX ctx; unsigned char sha1[20]; int ret; + if (open_pack_index(p)) + return error("packfile %s index not opened", p->pack_name); + index_size = p->index_size; + index_base = p->index_data; + ret = 0; /* Verify SHA1 sum of the index file */ SHA1_Init(&ctx); diff --git a/pack-redundant.c b/pack-redundant.c index 87077e150c..06173206f0 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -550,6 +550,9 @@ static struct pack_list * add_pack(struct packed_git *p) l.pack = p; llist_init(&l.all_objects); + if (!p->index_data && open_pack_index(p)) + return NULL; + base = p->index_data; base += 256 * 4 + ((p->index_version < 2) ? 4 : 8); step = (p->index_version < 2) ? 24 : 20; diff --git a/sha1_file.c b/sha1_file.c index 12d2ef2011..6a5ba63500 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -530,6 +530,21 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) return 0; } +int open_pack_index (struct packed_git *p) +{ + char *idx_name; + int ret; + + if (p->index_data) + return 0; + + idx_name = xstrdup(p->pack_name); + strcpy(idx_name + strlen(idx_name) - strlen(".pack"), ".idx"); + ret = check_packed_git_idx(idx_name, p); + free(idx_name); + return ret; +} + static void scan_windows(struct packed_git *p, struct packed_git **lru_p, struct pack_window **lru_w, @@ -605,6 +620,9 @@ static int open_packed_git_1(struct packed_git *p) unsigned char *idx_sha1; long fd_flag; + if (!p->index_data && open_pack_index(p)) + return error("packfile %s index unavailable", p->pack_name); + p->pack_fd = open(p->pack_name, O_RDONLY); if (p->pack_fd < 0 || fstat(p->pack_fd, &st)) return -1; @@ -757,8 +775,7 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) return NULL; memcpy(p->pack_name, path, path_len); strcpy(p->pack_name + path_len, ".pack"); - if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode) || - check_packed_git_idx(path, p)) { + if (stat(p->pack_name, &st) || !S_ISREG(st.st_mode)) { free(p); return NULL; } @@ -766,6 +783,10 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) /* ok, it looks sane as far as we can check without * actually mapping the pack file. */ + p->index_version = 0; + p->index_data = NULL; + p->index_size = 0; + p->num_objects = 0; p->pack_size = st.st_size; p->next = NULL; p->windows = NULL; @@ -1572,10 +1593,15 @@ void *unpack_entry(struct packed_git *p, off_t obj_offset, return data; } -const unsigned char *nth_packed_object_sha1(const struct packed_git *p, +const unsigned char *nth_packed_object_sha1(struct packed_git *p, uint32_t n) { const unsigned char *index = p->index_data; + if (!index) { + if (open_pack_index(p)) + return NULL; + index = p->index_data; + } if (n >= p->num_objects) return NULL; index += 4 * 256; @@ -1612,6 +1638,12 @@ off_t find_pack_entry_one(const unsigned char *sha1, const unsigned char *index = p->index_data; unsigned hi, lo; + if (!index) { + if (open_pack_index(p)) + return 0; + level1_ofs = p->index_data; + index = p->index_data; + } if (p->index_version > 1) { level1_ofs += 2; index += 8; From 7dc24aa5a62cc5f77e6637674581c837f4bdf78e Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 May 2007 01:24:40 -0400 Subject: [PATCH 10/37] Micro-optimize prepare_alt_odb Calling getenv() is not that expensive, but its also not free, and its certainly not cheaper than testing to see if alt_odb_tail is not null. Because we are calling prepare_alt_odb() from within find_sha1_file every time we cannot find an object file locally we want to skip out of prepare_alt_odb() as early as possible once we have initialized our alternate list. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 6a5ba63500..a3637d7e5b 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -376,11 +376,12 @@ void prepare_alt_odb(void) { const char *alt; + if (alt_odb_tail) + return; + alt = getenv(ALTERNATE_DB_ENVIRONMENT); if (!alt) alt = ""; - if (alt_odb_tail) - return; alt_odb_tail = &alt_odb_list; link_alt_odb_entries(alt, alt + strlen(alt), ':', NULL, 0); From 693d2bc625e7168299741d673e7205e9d2c969df Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Sat, 26 May 2007 01:25:11 -0400 Subject: [PATCH 11/37] Attempt to delay prepare_alt_odb during get_sha1 Not every input value passed to get_sha1 is an abbreviated SHA-1. Its actually quite common for refs to be passed and for those refs to resolve to full SHA-1s, in which case we may not need to initialize the alternate object database list in this process. I'm relocating the call to prepare_alt_odb closer to the code that actually needs it to maintain the fix first introduced by Junio in 99a19b43 (to avoid ambiguous SHA-1 abbreviations from being accepted). This allows us to avoid the alt_odb list setup if we won't actually need it. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_name.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1_name.c b/sha1_name.c index 55f25a2d3b..8dfceb2d7f 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -133,6 +133,7 @@ static int find_unique_short_object(int len, char *canonical, int has_unpacked, has_packed; unsigned char unpacked_sha1[20], packed_sha1[20]; + prepare_alt_odb(); has_unpacked = find_short_object_filename(len, canonical, unpacked_sha1); has_packed = find_short_packed_object(len, res, packed_sha1); if (!has_unpacked && !has_packed) @@ -654,7 +655,6 @@ int get_sha1_with_mode(const char *name, unsigned char *sha1, unsigned *mode) const char *cp; *mode = S_IFINVALID; - prepare_alt_odb(); ret = get_sha1_1(name, namelen, sha1); if (!ret) return ret; From a588d88aaff312f3afd5713ffcb4e4b1829fb5a6 Mon Sep 17 00:00:00 2001 From: Martin Koegler Date: Mon, 28 May 2007 23:20:57 +0200 Subject: [PATCH 12/37] builtin-pack-objects: don't fail, if delta is not possible If builtin-pack-objects runs out of memory while finding the best deltas, it bails out with an error. If the delta index creation fails (because there is not enough memory), we can downgrade the error message to a warning and continue with the next object. Signed-off-by: Martin Koegler Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index e52332df99..17627b34e8 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -1454,8 +1454,12 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, } if (!src->index) { src->index = create_delta_index(src->data, src_size); - if (!src->index) - die("out of memory"); + if (!src->index) { + static int warned = 0; + if (!warned++) + warning("suboptimal pack - out of memory"); + return 0; + } } delta_buf = create_delta(src->index, trg->data, trg_size, &delta_size, max_size); From 074b2eea296886e179ef73e1c364f370a223618a Mon Sep 17 00:00:00 2001 From: Martin Koegler Date: Mon, 28 May 2007 23:20:58 +0200 Subject: [PATCH 13/37] git-pack-objects: cache small deltas between big objects Creating deltas between big blobs is a CPU and memory intensive task. In the writing phase, all (not reused) deltas are redone. This patch adds support for caching deltas from the deltifing phase, so that that the writing phase is faster. The caching is limited to small deltas to avoid increasing memory usage very much. The implemented limit is (memory needed to create the delta)/1024. Signed-off-by: Martin Koegler Signed-off-by: Junio C Hamano --- Documentation/config.txt | 5 +++ builtin-pack-objects.c | 69 +++++++++++++++++++++++++++++++--------- 2 files changed, 59 insertions(+), 15 deletions(-) diff --git a/Documentation/config.txt b/Documentation/config.txt index 3d8f03dfe5..ab0f8f4865 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -567,6 +567,11 @@ pack.compression:: slowest. If not set, defaults to core.compression. If that is not set, defaults to -1. +pack.deltaCacheSize:: + The maxium memory in bytes used for caching deltas in + gitlink:git-pack-objects[1]. + A value of 0 means no limit. Defaults to 0. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 17627b34e8..9f035ba8e6 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -36,6 +36,7 @@ struct object_entry { struct object_entry *delta_sibling; /* other deltified objects who * uses the same base as me */ + void *delta_data; /* cached delta (uncompressed) */ unsigned long delta_size; /* delta data size (uncompressed) */ enum object_type type; enum object_type in_pack_type; /* could be delta */ @@ -76,6 +77,9 @@ static struct progress progress_state; static int pack_compression_level = Z_DEFAULT_COMPRESSION; static int pack_compression_seen; +static unsigned long delta_cache_size = 0; +static unsigned long max_delta_cache_size = 0; + /* * The object names in objects array are hashed with this hashtable, * to help looking up the entry by object name. @@ -405,24 +409,31 @@ static unsigned long write_object(struct sha1file *f, z_stream stream; unsigned long maxsize; void *out; - buf = read_sha1_file(entry->sha1, &type, &size); - if (!buf) - die("unable to read %s", sha1_to_hex(entry->sha1)); - if (size != entry->size) - die("object %s size inconsistency (%lu vs %lu)", - sha1_to_hex(entry->sha1), size, entry->size); - if (usable_delta) { - buf = delta_against(buf, size, entry); + if (entry->delta_data && usable_delta) { + buf = entry->delta_data; size = entry->delta_size; obj_type = (allow_ofs_delta && entry->delta->offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; } else { - /* - * recover real object type in case - * check_object() wanted to re-use a delta, - * but we couldn't since base was in previous split pack - */ - obj_type = type; + buf = read_sha1_file(entry->sha1, &type, &size); + if (!buf) + die("unable to read %s", sha1_to_hex(entry->sha1)); + if (size != entry->size) + die("object %s size inconsistency (%lu vs %lu)", + sha1_to_hex(entry->sha1), size, entry->size); + if (usable_delta) { + buf = delta_against(buf, size, entry); + size = entry->delta_size; + obj_type = (allow_ofs_delta && entry->delta->offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; + } else { + /* + * recover real object type in case + * check_object() wanted to re-use a delta, + * but we couldn't since base was in previous split pack + */ + obj_type = type; + } } /* compress the data to store and put compressed length in datalen */ memset(&stream, 0, sizeof(stream)); @@ -1385,6 +1396,20 @@ struct unpacked { struct delta_index *index; }; +static int delta_cacheable(struct unpacked *trg, struct unpacked *src, + unsigned long src_size, unsigned long trg_size, + unsigned long delta_size) +{ + if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size) + return 0; + + /* cache delta, if objects are large enough compared to delta size */ + if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) + return 1; + + return 0; +} + /* * We search for deltas _backwards_ in a list sorted by type and * by size, so that we see progressively smaller and smaller files. @@ -1466,10 +1491,20 @@ static int try_delta(struct unpacked *trg, struct unpacked *src, if (!delta_buf) return 0; + if (trg_entry->delta_data) { + delta_cache_size -= trg_entry->delta_size; + free(trg_entry->delta_data); + } + trg_entry->delta_data = 0; trg_entry->delta = src_entry; trg_entry->delta_size = delta_size; trg_entry->depth = src_entry->depth + 1; - free(delta_buf); + + if (delta_cacheable(src, trg, src_size, trg_size, delta_size)) { + trg_entry->delta_data = xrealloc(delta_buf, delta_size); + delta_cache_size += trg_entry->delta_size; + } else + free(delta_buf); return 1; } @@ -1615,6 +1650,10 @@ static int git_pack_config(const char *k, const char *v) pack_compression_seen = 1; return 0; } + if (!strcmp(k, "pack.deltacachesize")) { + max_delta_cache_size = git_config_int(k, v); + return 0; + } return git_default_config(k, v); } From e3dfddb377478dbee9c5b88636e97d62312f562d Mon Sep 17 00:00:00 2001 From: Martin Koegler Date: Mon, 28 May 2007 23:20:59 +0200 Subject: [PATCH 14/37] builtin-pack-object: cache small deltas Signed-off-by: Martin Koegler Signed-off-by: Junio C Hamano --- Documentation/config.txt | 4 ++++ builtin-pack-objects.c | 8 ++++++++ 2 files changed, 12 insertions(+) diff --git a/Documentation/config.txt b/Documentation/config.txt index ab0f8f4865..6ea4f10150 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -572,6 +572,10 @@ pack.deltaCacheSize:: gitlink:git-pack-objects[1]. A value of 0 means no limit. Defaults to 0. +pack.deltaCacheLimit:: + The maxium size of a delta, that is cached in + gitlink:git-pack-objects[1]. Defaults to 1000. + pull.octopus:: The default merge strategy to use when pulling multiple branches at once. diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 9f035ba8e6..41472fcbd0 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -79,6 +79,7 @@ static int pack_compression_seen; static unsigned long delta_cache_size = 0; static unsigned long max_delta_cache_size = 0; +static unsigned long cache_max_small_delta_size = 1000; /* * The object names in objects array are hashed with this hashtable, @@ -1403,6 +1404,9 @@ static int delta_cacheable(struct unpacked *trg, struct unpacked *src, if (max_delta_cache_size && delta_cache_size + delta_size > max_delta_cache_size) return 0; + if (delta_size < cache_max_small_delta_size) + return 1; + /* cache delta, if objects are large enough compared to delta size */ if ((src_size >> 20) + (trg_size >> 21) > (delta_size >> 10)) return 1; @@ -1654,6 +1658,10 @@ static int git_pack_config(const char *k, const char *v) max_delta_cache_size = git_config_int(k, v); return 0; } + if (!strcmp(k, "pack.deltacachelimit")) { + cache_max_small_delta_size = git_config_int(k, v); + return 0; + } return git_default_config(k, v); } From 1055880e7c096907ac87203dd83fdc6830251115 Mon Sep 17 00:00:00 2001 From: James Bowes Date: Tue, 29 May 2007 19:29:51 -0400 Subject: [PATCH 15/37] rev-parse: Identify short sha1 sums correctly. find_short_packed_object was not loading the pack index files. Teach it to do so. Signed-off-by: James Bowes Signed-off-by: Junio C Hamano --- sha1_name.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/sha1_name.c b/sha1_name.c index 8dfceb2d7f..7df01af788 100644 --- a/sha1_name.c +++ b/sha1_name.c @@ -76,8 +76,11 @@ static int find_short_packed_object(int len, const unsigned char *match, unsigne prepare_packed_git(); for (p = packed_git; p && found < 2; p = p->next) { - uint32_t num = p->num_objects; - uint32_t first = 0, last = num; + uint32_t num, last; + uint32_t first = 0; + open_pack_index(p); + num = p->num_objects; + last = num; while (first < last) { uint32_t mid = (first + last) / 2; const unsigned char *now; From 7ff895c0d229c2c60b73e91b0c389a4e3ce69e46 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 May 2007 00:50:26 -0400 Subject: [PATCH 16/37] Test for recent rev-parse $abbrev_sha1 regression My recent patch "Lazily open pack index files on demand" caused a regression in the case of parsing abbreviated SHA-1 object names. Git was unable to translate the abbreviated name into the full name if the object was packed, as the pack .idx files were not opened before being accessed. This is a simple test to repack a repository then test for an abbreviated SHA-1 within the packfile. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- t/t6101-rev-parse-parents.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/t/t6101-rev-parse-parents.sh b/t/t6101-rev-parse-parents.sh index 7d354a1fae..b0252b9413 100755 --- a/t/t6101-rev-parse-parents.sh +++ b/t/t6101-rev-parse-parents.sh @@ -29,5 +29,15 @@ test_expect_success 'final^1^3 not valid' "if git-rev-parse --verify final^1^3; test_expect_failure '--verify start2^1' 'git-rev-parse --verify start2^1' test_expect_success '--verify start2^0' 'git-rev-parse --verify start2^0' +test_expect_success 'repack for next test' 'git repack -a -d' +test_expect_success 'short SHA-1 works' ' + start=`git rev-parse --verify start` && + echo $start && + abbrv=`echo $start | sed s/.\$//` && + echo $abbrv && + abbrv=`git rev-parse --verify $abbrv` && + echo $abbrv && + test $start = $abbrv' + test_done From eaa867703927c1f383637979d16c40d996cea240 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 May 2007 02:12:28 -0400 Subject: [PATCH 17/37] Simplify index access condition in count-objects, pack-redundant My earlier lazy index opening patch changed this condition to check index_data and call open_pack_index if it was NULL. In truth we only care about num_objects. Since open_pack_index does no harm if the index is already open, and all indexes are likely to be closed in this application, the "performance optimization" of inlining the index_data check here was wrong. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-count-objects.c | 2 +- pack-redundant.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/builtin-count-objects.c b/builtin-count-objects.c index ac65e03e7f..4274ec1950 100644 --- a/builtin-count-objects.c +++ b/builtin-count-objects.c @@ -111,7 +111,7 @@ int cmd_count_objects(int ac, const char **av, const char *prefix) for (p = packed_git; p; p = p->next) { if (!p->pack_local) continue; - if (!p->index_data && open_pack_index(p)) + if (open_pack_index(p)) continue; packed += p->num_objects; num_pack++; diff --git a/pack-redundant.c b/pack-redundant.c index 06173206f0..6bc3bdf3f4 100644 --- a/pack-redundant.c +++ b/pack-redundant.c @@ -550,7 +550,7 @@ static struct pack_list * add_pack(struct packed_git *p) l.pack = p; llist_init(&l.all_objects); - if (!p->index_data && open_pack_index(p)) + if (open_pack_index(p)) return NULL; base = p->index_data; From b77ffe8a57a0921f58cff22dcf1ed6ae64d89d6a Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 May 2007 02:13:14 -0400 Subject: [PATCH 18/37] Ensure the pack index is opened before access In this particular location of fsck the index should have already been opened by verify_pack, which is called just before we get here and loop through the object names. However, just in case a future version of that function does not use the index file we'll double-check its open before we access the num_objects field. Better safe now than sorry later. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- builtin-fsck.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/builtin-fsck.c b/builtin-fsck.c index cbbcaf011a..9959818ced 100644 --- a/builtin-fsck.c +++ b/builtin-fsck.c @@ -668,7 +668,10 @@ int cmd_fsck(int argc, char **argv, const char *prefix) verify_pack(p, 0); for (p = packed_git; p; p = p->next) { - uint32_t i, num = p->num_objects; + uint32_t i, num; + if (open_pack_index(p)) + continue; + num = p->num_objects; for (i = 0; i < num; i++) fsck_sha1(nth_packed_object_sha1(p, i)); } From bc8e478a285ff549a3e5182461b064313d400de3 Mon Sep 17 00:00:00 2001 From: "Shawn O. Pearce" Date: Wed, 30 May 2007 02:13:42 -0400 Subject: [PATCH 19/37] Style nit - don't put space after function names Our style is to not put a space after a function name. I did here, and Junio applied the patch with the incorrect formatting. So I'm cleaning up after myself since I noticed it upon review. Signed-off-by: Shawn O. Pearce Signed-off-by: Junio C Hamano --- sha1_file.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sha1_file.c b/sha1_file.c index a3637d7e5b..3093ac9f5f 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -531,7 +531,7 @@ static int check_packed_git_idx(const char *path, struct packed_git *p) return 0; } -int open_pack_index (struct packed_git *p) +int open_pack_index(struct packed_git *p) { char *idx_name; int ret; From 65c6aca4d4c6fabc2767b8fa1ce7e67d3d594b1e Mon Sep 17 00:00:00 2001 From: Junio C Hamano Date: Tue, 29 May 2007 17:55:47 -0700 Subject: [PATCH 20/37] Add DLH to .mailmap ... and make the entries sorted. --- .mailmap | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.mailmap b/.mailmap index 4e0615e9be..aa8ee6b3f1 100644 --- a/.mailmap +++ b/.mailmap @@ -7,6 +7,8 @@ Aneesh Kumar K.V Chris Shoemaker +Dana L. How +Dana L. How Daniel Barkalow David Kågedal Fredrik Kuivinen @@ -19,8 +21,8 @@ Jon Loeliger Jon Seymour Karl Hasselström Kent Engstrom -Lars Doelle Lars Doelle +Lars Doelle Lukas Sandström Martin Langhoff Michele Ballabio @@ -34,12 +36,11 @@ Sean Estabrooks Shawn O. Pearce Theodore Ts'o Tony Luck -Uwe Kleine-König Uwe Kleine-König -Uwe Kleine-König Uwe Kleine-König +Uwe Kleine-König +Uwe Kleine-König Ville Skyttä YOSHIFUJI Hideaki anonymous anonymous -Dana L. How From 5c5ba73b21a6910ee67d97cb87a5d78409112375 Mon Sep 17 00:00:00 2001 From: Julian Phillips Date: Thu, 31 May 2007 00:18:24 +0100 Subject: [PATCH 21/37] Makefile: Use generic rule to build test programs Use a generic make rule to build all the test programs, rather than specifically mentioning each one. Signed-off-by: Julian Phillips Signed-off-by: Junio C Hamano --- Makefile | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/Makefile b/Makefile index 75277343f6..cac0a4a2ed 100644 --- a/Makefile +++ b/Makefile @@ -942,7 +942,7 @@ endif ### Testing rules -TEST_PROGRAMS = test-chmtime$X test-genrandom$X +TEST_PROGRAMS = test-chmtime$X test-genrandom$X test-date$X test-delta$X test-sha1$X test-match-trees$X all:: $(TEST_PROGRAMS) @@ -955,26 +955,12 @@ export NO_SVN_TESTS test: all $(MAKE) -C t/ all -test-date$X: test-date.c date.o ctype.o - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) test-date.c date.o ctype.o +test-date$X: date.o ctype.o -test-delta$X: test-delta.o diff-delta.o patch-delta.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) +test-delta$X: diff-delta.o patch-delta.o -test-dump-cache-tree$X: dump-cache-tree.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) - -test-sha1$X: test-sha1.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) - -test-match-trees$X: test-match-trees.o $(GITLIBS) - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) - -test-chmtime$X: test-chmtime.c - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $< - -test-genrandom$X: test-genrandom.c - $(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $< +test-%$X: test-%.o $(GITLIBS) + $(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) $(LIBS) check-sha1:: test-sha1$X ./test-sha1.sh From 5476a8adcc29985e5496dac7a340dfd178f43a17 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 30 May 2007 21:43:12 -0400 Subject: [PATCH 22/37] fix repack with --max-pack-size Two issues here: 1) git-repack -a --max-pack-size=10 on the GIT repo dies pretty quick. There is a lot of confusion about deltas that were suposed to be reused from another pack but that get stored undeltified due to pack limit and object size doesn't match entry->size anymore. This test is not really worth the complexity for determining when it is valid so get rid of it. 2) If pack limit is reached, the object buffer is freed, including when it comes from a cached delta data. In practice the object will be stored in a subsequent pack undeltified, but let's make sure no pointer to freed data subsists by clearing entry->delta_data. I also reorganized that code a bit to make it more readable. Signed-off-by: Nicolas Pitre Signed-off-by: Junio C Hamano --- builtin-pack-objects.c | 29 +++++++++++------------------ 1 file changed, 11 insertions(+), 18 deletions(-) diff --git a/builtin-pack-objects.c b/builtin-pack-objects.c index 41472fcbd0..ccb25f6a9c 100644 --- a/builtin-pack-objects.c +++ b/builtin-pack-objects.c @@ -410,31 +410,24 @@ static unsigned long write_object(struct sha1file *f, z_stream stream; unsigned long maxsize; void *out; - if (entry->delta_data && usable_delta) { - buf = entry->delta_data; + if (!usable_delta) { + buf = read_sha1_file(entry->sha1, &obj_type, &size); + if (!buf) + die("unable to read %s", sha1_to_hex(entry->sha1)); + } else if (entry->delta_data) { size = entry->delta_size; + buf = entry->delta_data; + entry->delta_data = NULL; obj_type = (allow_ofs_delta && entry->delta->offset) ? OBJ_OFS_DELTA : OBJ_REF_DELTA; } else { buf = read_sha1_file(entry->sha1, &type, &size); if (!buf) die("unable to read %s", sha1_to_hex(entry->sha1)); - if (size != entry->size) - die("object %s size inconsistency (%lu vs %lu)", - sha1_to_hex(entry->sha1), size, entry->size); - if (usable_delta) { - buf = delta_against(buf, size, entry); - size = entry->delta_size; - obj_type = (allow_ofs_delta && entry->delta->offset) ? - OBJ_OFS_DELTA : OBJ_REF_DELTA; - } else { - /* - * recover real object type in case - * check_object() wanted to re-use a delta, - * but we couldn't since base was in previous split pack - */ - obj_type = type; - } + buf = delta_against(buf, size, entry); + size = entry->delta_size; + obj_type = (allow_ofs_delta && entry->delta->offset) ? + OBJ_OFS_DELTA : OBJ_REF_DELTA; } /* compress the data to store and put compressed length in datalen */ memset(&stream, 0, sizeof(stream)); From f7c22cc68ccba0cb5bbd43177507795c48afb1f5 Mon Sep 17 00:00:00 2001 From: Nicolas Pitre Date: Wed, 30 May 2007 22:48:13 -0400 Subject: [PATCH 23/37] always start looking up objects in the last used pack first Jon Smirl said: | Once an object reference hits a pack file it is very likely that | following references will hit the same pack file. So first place to | look for an object is the same place the previous object was found. This is indeed a good heuristic so here it is. The search always start with the pack where the last object lookup succeeded. If the wanted object is not available there then the search continues with the normal pack ordering. To test this I split the Linux repository into 66 packs and performed a "time git-rev-list --objects --all > /dev/null". Best results are as follows: Pack Sort w/o this patch w/ this patch ------------------------------------------------------------- recent objects last 26.4s 20.9s recent objects first 24.9s 18.4s This shows that the pack order based on object age has some influence, but that the last-used-pack heuristic is even more significant in reducing object lookup. Signed-off-by: Nicolas Pitre --- Note: the --max-pack-size to git-repack currently produces packs with old objects after those containing recent objects. The pack sort based on filesystem timestamp is therefore backward for those. This needs to be fixed of course, but at least it made me think about this variable for the test. Signed-off-by: Junio C Hamano --- sha1_file.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/sha1_file.c b/sha1_file.c index 12d2ef2011..cf855ac244 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -1654,20 +1654,25 @@ static int matches_pack_name(struct packed_git *p, const char *ig) static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, const char **ignore_packed) { + static struct packed_git *last_found = (void *)1; struct packed_git *p; off_t offset; prepare_packed_git(); + if (!packed_git) + return 0; + p = (last_found == (void *)1) ? packed_git : last_found; - for (p = packed_git; p; p = p->next) { + do { if (ignore_packed) { const char **ig; for (ig = ignore_packed; *ig; ig++) if (!matches_pack_name(p, *ig)) break; if (*ig) - continue; + goto next; } + offset = find_pack_entry_one(sha1, p); if (offset) { /* @@ -1680,14 +1685,23 @@ static int find_pack_entry(const unsigned char *sha1, struct pack_entry *e, cons */ if (p->pack_fd == -1 && open_packed_git(p)) { error("packfile %s cannot be accessed", p->pack_name); - continue; + goto next; } e->offset = offset; e->p = p; hashcpy(e->sha1, sha1); + last_found = p; return 1; } - } + + next: + if (p == last_found) + p = packed_git; + else + p = p->next; + if (p == last_found) + p = p->next; + } while (p); return 0; } From b75c6c6de1e8f801edb142b59e7809a166a63adc Mon Sep 17 00:00:00 2001 From: Martin Koegler Date: Tue, 29 May 2007 21:08:35 +0200 Subject: [PATCH 24/37] diff-delta: use realloc instead of xrealloc Commit 83572c1a914d3f7a8dd66d954c11bbc665b7b923 changed many realloc to xrealloc. This change was made in diff-delta.c too, although the code can handle an out of memory failure. This patch reverts this change in diff-delta.c. Signed-off-by: Martin Koegler Signed-off-by: Junio C Hamano --- diff-delta.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/diff-delta.c b/diff-delta.c index 17757d2af9..faf96e4713 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -388,7 +388,7 @@ create_delta(const struct delta_index *index, outsize = max_size + MAX_OP_SIZE + 1; if (max_size && outpos > max_size) break; - out = xrealloc(out, outsize); + out = realloc(out, outsize); if (!out) { free(tmp); return NULL; From 5049012f4f9fc754c68d5e89ddabd4f664cea0a7 Mon Sep 17 00:00:00 2001 From: Theodore Ts'o Date: Thu, 31 May 2007 19:00:48 -0400 Subject: [PATCH 25/37] Fix minor grammatical typos in the git-gc man page Signed-off-by: "Theodore Ts'o" Signed-off-by: Junio C Hamano --- Documentation/git-gc.txt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Documentation/git-gc.txt b/Documentation/git-gc.txt index 4ac839f938..c7742ca963 100644 --- a/Documentation/git-gc.txt +++ b/Documentation/git-gc.txt @@ -37,10 +37,10 @@ OPTIONS --aggressive:: Usually 'git-gc' runs very quickly while providing good disk - space utilization and performance. This option will cause - git-gc to more aggressive optimize the repository at the expense + space utilization and performance. This option will cause + git-gc to more aggressively optimize the repository at the expense of taking much more time. The effects of this optimization are - persistent, so this option only needs to be sporadically; every + persistent, so this option only needs to be used occasionally; every few hundred changesets or so. Configuration From 302665473cfe233516cc4c29a1a0359153e5506d Mon Sep 17 00:00:00 2001 From: Jonas Fonseca Date: Sat, 2 Jun 2007 19:56:44 +0200 Subject: [PATCH 26/37] Fix git-am(1) synopsis formatting Signed-off-by: Jonas Fonseca Signed-off-by: Junio C Hamano --- Documentation/git-am.txt | 1 - 1 file changed, 1 deletion(-) diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 25cf84a0c7..f78e5dc28d 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -13,7 +13,6 @@ SYNOPSIS [--3way] [--interactive] [--binary] [--whitespace=