diff options
49 files changed, 1131 insertions, 459 deletions
diff --git a/Documentation/config.txt b/Documentation/config.txt index 6452a8be14..178e0e1e20 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -267,6 +267,10 @@ i18n.commitEncoding:: browser (and possibly at other places in the future or in other porcelains). See e.g. gitlink:git-mailinfo[1]. Defaults to 'utf-8'. +i18n.logOutputEncoding:: + Character encoding the commit messages are converted to when + running `git-log` and friends. + log.showroot:: If true, the initial commit will be shown as a big creation event. This is equivalent to a diff against an empty tree. diff --git a/Documentation/git-merge.txt b/Documentation/git-merge.txt index e2954aa76e..0f79665ea6 100644 --- a/Documentation/git-merge.txt +++ b/Documentation/git-merge.txt @@ -10,7 +10,6 @@ SYNOPSIS -------- [verse] 'git-merge' [-n] [--no-commit] [--squash] [-s <strategy>]... - [--reflog-action=<action>] -m=<msg> <remote> <remote>... DESCRIPTION @@ -37,11 +36,6 @@ include::merge-options.txt[] least one <remote>. Specifying more than one <remote> obviously means you are trying an Octopus. ---reflog-action=<action>:: - This is used internally when `git-pull` calls this command - to record that the merge was created by `pull` command - in the `ref-log` entry that results from the merge. - include::merge-strategies.txt[] @@ -79,6 +79,10 @@ all: # # Define NO_ICONV if your libc does not properly support iconv. # +# Define NO_R_TO_GCC if your gcc does not like "-R/path/lib" that +# tells runtime paths to dynamic libraries; "-Wl,-rpath=/path/lib" +# is used instead. +# # Define USE_NSEC below if you want git to care about sub-second file mtimes # and ctimes. Note that you need recent glibc (at least 2.2.4) for this, and # it will BREAK YOUR LOCAL DIFFS! show-diff and anything using it will likely @@ -250,8 +254,7 @@ LIB_OBJS = \ revision.o pager.o tree-walk.o xdiff-interface.o \ write_or_die.o trace.o list-objects.o grep.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 \ - utf8.o + color.o wt-status.o archive-zip.o archive-tar.o shallow.o utf8.o BUILTIN_OBJS = \ builtin-add.o \ @@ -422,11 +425,19 @@ ifeq ($(uname_S),Darwin) endif endif +ifdef NO_R_TO_GCC_LINKER + # Some gcc does not accept and pass -R to the linker to specify + # the runtime dynamic library path. + CC_LD_DYNPATH = -Wl,-rpath= +else + CC_LD_DYNPATH = -R +endif + ifndef NO_CURL ifdef CURLDIR - # This is still problematic -- gcc does not always want -R. + # Try "-Wl,-rpath=$(CURLDIR)/lib" in such a case. BASIC_CFLAGS += -I$(CURLDIR)/include - CURL_LIBCURL = -L$(CURLDIR)/lib -R$(CURLDIR)/lib -lcurl + CURL_LIBCURL = -L$(CURLDIR)/lib $(CC_LD_DYNPATH)$(CURLDIR)/lib -lcurl else CURL_LIBCURL = -lcurl endif @@ -445,9 +456,8 @@ endif ifndef NO_OPENSSL OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR - # Again this may be problematic -- gcc does not always want -R. BASIC_CFLAGS += -I$(OPENSSLDIR)/include - OPENSSL_LINK = -L$(OPENSSLDIR)/lib -R$(OPENSSLDIR)/lib + OPENSSL_LINK = -L$(OPENSSLDIR)/lib $(CC_LD_DYNPATH)$(OPENSSLDIR)/lib else OPENSSL_LINK = endif @@ -463,9 +473,8 @@ else endif ifdef NEEDS_LIBICONV ifdef ICONVDIR - # Again this may be problematic -- gcc does not always want -R. BASIC_CFLAGS += -I$(ICONVDIR)/include - ICONV_LINK = -L$(ICONVDIR)/lib -R$(ICONVDIR)/lib + ICONV_LINK = -L$(ICONVDIR)/lib $(CC_LD_DYNPATH)$(ICONVDIR)/lib else ICONV_LINK = endif diff --git a/builtin-add.c b/builtin-add.c index 8ed4a6a9f3..e7a1b4d9ab 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -26,18 +26,9 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p i = dir->nr; while (--i >= 0) { struct dir_entry *entry = *src++; - int how = match_pathspec(pathspec, entry->name, entry->len, - prefix, seen); - /* - * ignored entries can be added with exact match, - * but not with glob nor recursive. - */ - if (!how || - (entry->ignored_entry && how != MATCHED_EXACTLY)) { - free(entry); - continue; - } - *dst++ = entry; + if (match_pathspec(pathspec, entry->name, entry->len, + prefix, seen)) + *dst++ = entry; } dir->nr = dst - dir->entries; @@ -47,10 +38,20 @@ static void prune_directory(struct dir_struct *dir, const char **pathspec, int p if (seen[i]) continue; - /* Existing file? We must have ignored it */ match = pathspec[i]; - if (!match[0] || !lstat(match, &st)) + if (!match[0]) continue; + + /* Existing file? We must have ignored it */ + if (!lstat(match, &st)) { + struct dir_entry *ent; + + ent = dir_add_name(dir, match, strlen(match)); + ent->ignored = 1; + if (S_ISDIR(st.st_mode)) + ent->ignored_dir = 1; + continue; + } die("pathspec '%s' did not match any files", match); } } @@ -62,8 +63,6 @@ static void fill_directory(struct dir_struct *dir, const char **pathspec) /* Set up the default git porcelain excludes */ memset(dir, 0, sizeof(*dir)); - if (pathspec) - dir->show_both = 1; dir->exclude_per_dir = ".gitignore"; path = git_path("info/exclude"); if (!access(path, R_OK)) @@ -154,7 +153,7 @@ int cmd_add(int argc, const char **argv, const char *prefix) if (show_only) { const char *sep = "", *eof = ""; for (i = 0; i < dir.nr; i++) { - if (!ignored_too && dir.entries[i]->ignored_entry) + if (!ignored_too && dir.entries[i]->ignored) continue; printf("%s%s", sep, dir.entries[i]->name); sep = " "; @@ -168,16 +167,19 @@ int cmd_add(int argc, const char **argv, const char *prefix) die("index file corrupt"); if (!ignored_too) { - int has_ignored = -1; - for (i = 0; has_ignored < 0 && i < dir.nr; i++) - if (dir.entries[i]->ignored_entry) - has_ignored = i; - if (0 <= has_ignored) { + int has_ignored = 0; + for (i = 0; i < dir.nr; i++) + if (dir.entries[i]->ignored) + has_ignored = 1; + if (has_ignored) { fprintf(stderr, ignore_warning); - for (i = has_ignored; i < dir.nr; i++) { - if (!dir.entries[i]->ignored_entry) + for (i = 0; i < dir.nr; i++) { + if (!dir.entries[i]->ignored) continue; - fprintf(stderr, "%s\n", dir.entries[i]->name); + fprintf(stderr, "%s", dir.entries[i]->name); + if (dir.entries[i]->ignored_dir) + fprintf(stderr, " (directory)"); + fputc('\n', stderr); } fprintf(stderr, "Use -f if you really want to add them.\n"); diff --git a/builtin-commit-tree.c b/builtin-commit-tree.c index f641787988..146aaffd28 100644 --- a/builtin-commit-tree.c +++ b/builtin-commit-tree.c @@ -92,6 +92,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) char comment[1000]; char *buffer; unsigned int size; + int encoding_is_utf8; setup_ident(); git_config(git_default_config); @@ -117,6 +118,10 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) parents++; } + /* Not having i18n.commitencoding is the same as having utf-8 */ + encoding_is_utf8 = (!git_commit_encoding || + !strcmp(git_commit_encoding, "utf-8")); + init_buffer(&buffer, &size); add_buffer(&buffer, &size, "tree %s\n", sha1_to_hex(tree_sha1)); @@ -130,7 +135,11 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) /* Person/date information */ add_buffer(&buffer, &size, "author %s\n", git_author_info(1)); - add_buffer(&buffer, &size, "committer %s\n\n", git_committer_info(1)); + add_buffer(&buffer, &size, "committer %s\n", git_committer_info(1)); + if (!encoding_is_utf8) + add_buffer(&buffer, &size, + "encoding %s\n", git_commit_encoding); + add_buffer(&buffer, &size, "\n"); /* And add the comment */ while (fgets(comment, sizeof(comment), stdin) != NULL) @@ -138,7 +147,7 @@ int cmd_commit_tree(int argc, const char **argv, const char *prefix) /* And check the encoding */ buffer[size] = '\0'; - if (!strcmp(git_commit_encoding, "utf-8") && !is_utf8(buffer)) + if (encoding_is_utf8 && !is_utf8(buffer)) fprintf(stderr, commit_utf8_warn); if (!write_sha1_file(buffer, size, commit_type, commit_sha1)) { diff --git a/builtin-log.c b/builtin-log.c index 8df3c1394a..a59b4acef1 100644 --- a/builtin-log.c +++ b/builtin-log.c @@ -20,6 +20,8 @@ void add_head(struct rev_info *revs); static void cmd_log_init(int argc, const char **argv, const char *prefix, struct rev_info *rev) { + int i; + rev->abbrev = DEFAULT_ABBREV; rev->commit_format = CMIT_FMT_DEFAULT; rev->verbose_header = 1; @@ -27,8 +29,18 @@ static void cmd_log_init(int argc, const char **argv, const char *prefix, argc = setup_revisions(argc, argv, rev, "HEAD"); if (rev->diffopt.pickaxe || rev->diffopt.filter) rev->always_show_header = 0; - if (argc > 1) - die("unrecognized argument: %s", argv[1]); + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (!strncmp(arg, "--encoding=", 11)) { + arg += 11; + if (strcmp(arg, "none")) + git_log_output_encoding = strdup(arg); + else + git_log_output_encoding = ""; + } + else + die("unrecognized argument: %s", arg); + } } static int cmd_log_walk(struct rev_info *rev) diff --git a/builtin-mailinfo.c b/builtin-mailinfo.c index e6472293d4..a67f3eb90b 100644 --- a/builtin-mailinfo.c +++ b/builtin-mailinfo.c @@ -4,6 +4,7 @@ */ #include "cache.h" #include "builtin.h" +#include "utf8.h" static FILE *cmitmsg, *patchfile, *fin, *fout; @@ -510,40 +511,18 @@ static int decode_b_segment(char *in, char *ot, char *ep) static void convert_to_utf8(char *line, char *charset) { -#ifndef NO_ICONV - char *in, *out; - size_t insize, outsize, nrc; - char outbuf[4096]; /* cheat */ static char latin_one[] = "latin1"; char *input_charset = *charset ? charset : latin_one; - iconv_t conv = iconv_open(metainfo_charset, input_charset); - - if (conv == (iconv_t) -1) { - static int warned_latin1_once = 0; - if (input_charset != latin_one) { - fprintf(stderr, "cannot convert from %s to %s\n", - input_charset, metainfo_charset); - *charset = 0; - } - else if (!warned_latin1_once) { - warned_latin1_once = 1; - fprintf(stderr, "tried to convert from %s to %s, " - "but your iconv does not work with it.\n", - input_charset, metainfo_charset); - } + char *out = reencode_string(line, metainfo_charset, input_charset); + + if (!out) { + fprintf(stderr, "cannot convert from %s to %s\n", + input_charset, metainfo_charset); + *charset = 0; return; } - in = line; - insize = strlen(in); - out = outbuf; - outsize = sizeof(outbuf); - nrc = iconv(conv, &in, &insize, &out, &outsize); - iconv_close(conv); - if (nrc == (size_t) -1) - return; - *out = 0; - strcpy(line, outbuf); -#endif + strcpy(line, out); + free(out); } static int decode_header_bq(char *it) @@ -827,7 +806,8 @@ int cmd_mailinfo(int argc, const char **argv, const char *prefix) if (!strcmp(argv[1], "-k")) keep_subject = 1; else if (!strcmp(argv[1], "-u")) - metainfo_charset = git_commit_encoding; + metainfo_charset = (git_commit_encoding + ? git_commit_encoding : "utf-8"); else if (!strncmp(argv[1], "--encoding=", 11)) metainfo_charset = argv[1] + 11; else @@ -416,8 +416,8 @@ extern int check_repository_format_version(const char *var, const char *value); extern char git_default_email[MAX_GITNAME]; extern char git_default_name[MAX_GITNAME]; -#define MAX_ENCODING_LENGTH 64 -extern char git_commit_encoding[MAX_ENCODING_LENGTH]; +extern char *git_commit_encoding; +extern char *git_log_output_encoding; extern int copy_fd(int ifd, int ofd); extern void write_or_die(int fd, const void *buf, size_t count); @@ -1,6 +1,8 @@ #include "cache.h" #include "tag.h" #include "commit.h" +#include "pkt-line.h" +#include "utf8.h" int save_commit_buffer = 1; @@ -221,6 +223,8 @@ static void prepare_commit_graft(void) return; graft_file = get_graft_file(); read_graft_file(graft_file); + /* make sure shallows are read */ + is_repository_shallow(); commit_graft_prepared = 1; } @@ -234,6 +238,37 @@ static struct commit_graft *lookup_commit_graft(const unsigned char *sha1) return commit_graft[pos]; } +int write_shallow_commits(int fd, int use_pack_protocol) +{ + int i, count = 0; + for (i = 0; i < commit_graft_nr; i++) + if (commit_graft[i]->nr_parent < 0) { + const char *hex = + sha1_to_hex(commit_graft[i]->sha1); + count++; + if (use_pack_protocol) + packet_write(fd, "shallow %s", hex); + else { + write(fd, hex, 40); + write(fd, "\n", 1); + } + } + return count; +} + +int unregister_shallow(const unsigned char *sha1) +{ + int pos = commit_graft_pos(sha1); + if (pos < 0) + return -1; + if (pos + 1 < commit_graft_nr) + memcpy(commit_graft + pos, commit_graft + pos + 1, + sizeof(struct commit_graft *) + * (commit_graft_nr - pos - 1)); + commit_graft_nr--; + return 0; +} + int parse_commit_buffer(struct commit *item, void *buffer, unsigned long size) { char *tail = buffer; @@ -563,10 +598,61 @@ static int add_merge_info(enum cmit_fmt fmt, char *buf, const struct commit *com return offset; } -unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit, - unsigned long len, char *buf, unsigned long space, +static char *get_header(const struct commit *commit, const char *key) +{ + int key_len = strlen(key); + const char *line = commit->buffer; + + for (;;) { + const char *eol = strchr(line, '\n'), *next; + + if (line == eol) + return NULL; + if (!eol) { + eol = line + strlen(line); + next = NULL; + } else + next = eol + 1; + if (!strncmp(line, key, key_len) && line[key_len] == ' ') { + int len = eol - line - key_len; + char *ret = xmalloc(len); + memcpy(ret, line + key_len + 1, len - 1); + ret[len - 1] = '\0'; + return ret; + } + line = next; + } +} + +static char *logmsg_reencode(const struct commit *commit) +{ + char *encoding; + char *out; + char *output_encoding = (git_log_output_encoding + ? git_log_output_encoding + : git_commit_encoding); + + if (!output_encoding) + return NULL; + encoding = get_header(commit, "encoding"); + if (!encoding || !strcmp(encoding, output_encoding)) { + free(encoding); + return NULL; + } + out = reencode_string(commit->buffer, output_encoding, encoding); + free(encoding); + if (!out) + return NULL; + return out; +} + +unsigned long pretty_print_commit(enum cmit_fmt fmt, + const struct commit *commit, + unsigned long len, + char *buf, unsigned long space, int abbrev, const char *subject, - const char *after_subject, int relative_date) + const char *after_subject, + int relative_date) { int hdr = 1, body = 0; unsigned long offset = 0; @@ -574,6 +660,10 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit int parents_shown = 0; const char *msg = commit->buffer; int plain_non_ascii = 0; + char *reencoded = logmsg_reencode(commit); + + if (reencoded) + msg = reencoded; if (fmt == CMIT_FMT_ONELINE || fmt == CMIT_FMT_EMAIL) indent = 0; @@ -590,7 +680,7 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit for (in_body = i = 0; (ch = msg[i]) && i < len; i++) { if (!in_body) { /* author could be non 7-bit ASCII but - * the log may so; skip over the + * the log may be so; skip over the * header part first. */ if (ch == '\n' && @@ -721,6 +811,8 @@ unsigned long pretty_print_commit(enum cmit_fmt fmt, const struct commit *commit if (fmt == CMIT_FMT_EMAIL && !body) buf[offset++] = '\n'; buf[offset] = '\0'; + + free(reencoded); return offset; } @@ -97,7 +97,7 @@ void sort_in_topological_order_fn(struct commit_list ** list, int lifo, struct commit_graft { unsigned char sha1[20]; - int nr_parent; + int nr_parent; /* < 0 if shallow commit */ unsigned char parent[FLEX_ARRAY][20]; /* more */ }; @@ -107,5 +107,12 @@ int read_graft_file(const char *graft_file); extern struct commit_list *get_merge_bases(struct commit *rev1, struct commit *rev2, int cleanup); +extern int register_shallow(const unsigned char *sha1); +extern int unregister_shallow(const unsigned char *sha1); +extern int write_shallow_commits(int fd, int use_pack_protocol); +extern int is_repository_shallow(); +extern struct commit_list *get_shallow_commits(struct object_array *heads, + int depth, int shallow_flag, int not_shallow_flag); + int in_merge_bases(struct commit *rev1, struct commit *rev2); #endif /* COMMIT_H */ @@ -309,10 +309,16 @@ int git_default_config(const char *var, const char *value) } if (!strcmp(var, "i18n.commitencoding")) { - strlcpy(git_commit_encoding, value, sizeof(git_commit_encoding)); + git_commit_encoding = strdup(value); return 0; } + if (!strcmp(var, "i18n.logoutputencoding")) { + git_log_output_encoding = strdup(value); + return 0; + } + + if (!strcmp(var, "pager.color") || !strcmp(var, "color.pager")) { pager_use_color = git_config_bool(var,value); return 0; diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 234cd0954b..7c7520ea29 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -711,6 +711,7 @@ _git_repo_config () core.compression core.legacyHeaders i18n.commitEncoding + i18n.logOutputEncoding diff.color color.diff diff.renameLimit @@ -260,13 +260,12 @@ int excluded(struct dir_struct *dir, const char *pathname) return 0; } -static void add_name(struct dir_struct *dir, const char *pathname, int len, - int ignored_entry) +struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len) { struct dir_entry *ent; if (cache_name_pos(pathname, len) >= 0) - return; + return NULL; if (dir->nr == dir->alloc) { int alloc = alloc_nr(dir->alloc); @@ -274,11 +273,12 @@ static void add_name(struct dir_struct *dir, const char *pathname, int len, dir->entries = xrealloc(dir->entries, alloc*sizeof(ent)); } ent = xmalloc(sizeof(*ent) + len + 1); - ent->ignored_entry = ignored_entry; + ent->ignored = ent->ignored_dir = 0; ent->len = len; memcpy(ent->name, pathname, len); ent->name[len] = 0; dir->entries[dir->nr++] = ent; + return ent; } static int dir_exists(const char *dirname, int len) @@ -316,7 +316,6 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co while ((de = readdir(fdir)) != NULL) { int len; - int ignored_entry; if ((de->d_name[0] == '.') && (de->d_name[1] == 0 || @@ -325,12 +324,11 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co continue; len = strlen(de->d_name); memcpy(fullname + baselen, de->d_name, len+1); - ignored_entry = excluded(dir, fullname); - - if (!dir->show_both && - (ignored_entry != dir->show_ignored) && - (!dir->show_ignored || DTYPE(de) != DT_DIR)) - continue; + if (excluded(dir, fullname) != dir->show_ignored) { + if (!dir->show_ignored || DTYPE(de) != DT_DIR) { + continue; + } + } switch (DTYPE(de)) { struct stat st; @@ -368,8 +366,7 @@ static int read_directory_recursive(struct dir_struct *dir, const char *path, co if (check_only) goto exit_early; else - add_name(dir, fullname, baselen + len, - ignored_entry); + dir_add_name(dir, fullname, baselen + len); } exit_early: closedir(fdir); @@ -13,8 +13,9 @@ struct dir_entry { - unsigned ignored_entry : 1; - unsigned int len : 15; + unsigned int ignored : 1; + unsigned int ignored_dir : 1; + unsigned int len : 30; char name[FLEX_ARRAY]; /* more */ }; @@ -30,8 +31,7 @@ struct exclude_list { struct dir_struct { int nr, alloc; - unsigned int show_both: 1, - show_ignored:1, + unsigned int show_ignored:1, show_other_directories:1, hide_empty_directories:1; struct dir_entry **entries; @@ -57,5 +57,6 @@ extern void add_excludes_from_file(struct dir_struct *, const char *fname); extern void add_exclude(const char *string, const char *base, int baselen, struct exclude_list *which); extern int file_exists(const char *); +extern struct dir_entry *dir_add_name(struct dir_struct *dir, const char *pathname, int len); #endif diff --git a/environment.c b/environment.c index f8c7dbcead..a1502c4e87 100644 --- a/environment.c +++ b/environment.c @@ -18,7 +18,8 @@ int prefer_symlink_refs; int log_all_ref_updates; int warn_ambiguous_refs = 1; int repository_format_version; -char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; +char *git_commit_encoding; +char *git_log_output_encoding; int shared_repository = PERM_UMASK; const char *apply_default_whitespace; int zlib_compression_level = Z_DEFAULT_COMPRESSION; diff --git a/fetch-pack.c b/fetch-pack.c index 92322cf4da..c527bf9e96 100644 --- a/fetch-pack.c +++ b/fetch-pack.c @@ -10,8 +10,9 @@ static int keep_pack; static int quiet; static int verbose; static int fetch_all; +static int depth; static const char fetch_pack_usage[] = -"git-fetch-pack [--all] [-q] [-v] [-k] [--thin] [--exec=upload-pack] [host:]directory <refs>..."; +"git-fetch-pack [--all] [-q] [-v] [-k] [--thin] [--exec=upload-pack] [--depth=<n>] [host:]directory <refs>..."; static const char *exec = "git-upload-pack"; #define COMPLETE (1U << 0) @@ -179,10 +180,41 @@ static int find_common(int fd[2], unsigned char *result_sha1, packet_write(fd[1], "want %s\n", sha1_to_hex(remote)); fetching++; } + if (is_repository_shallow()) + write_shallow_commits(fd[1], 1); + if (depth > 0) + packet_write(fd[1], "deepen %d", depth); packet_flush(fd[1]); if (!fetching) return 1; + if (depth > 0) { + char line[1024]; + unsigned char sha1[20]; + int len; + + while ((len = packet_read_line(fd[0], line, sizeof(line)))) { + if (!strncmp("shallow ", line, 8)) { + if (get_sha1_hex(line + 8, sha1)) + die("invalid shallow line: %s", line); + register_shallow(sha1); + continue; + } + if (!strncmp("unshallow ", line, 10)) { + if (get_sha1_hex(line + 10, sha1)) + die("invalid unshallow line: %s", line); + if (!lookup_object(sha1)) + die("object not found: %s", line); + /* make sure that it is parsed as shallow */ + parse_object(sha1); + if (unregister_shallow(sha1)) + die("no shallow found: %s", line); + continue; + } + die("expected shallow/unshallow, got %s", line); + } + } + flushes = 0; retval = -1; while ((sha1 = get_rev())) { @@ -309,7 +341,8 @@ static void filter_refs(struct ref **refs, int nr_match, char **match) if (!memcmp(ref->name, "refs/", 5) && check_ref_format(ref->name + 5)) ; /* trash */ - else if (fetch_all) { + else if (fetch_all && + (!depth || strncmp(ref->name, "refs/tags/", 10) )) { *newtail = ref; ref->next = NULL; newtail = &ref->next; @@ -368,9 +401,11 @@ static int everything_local(struct ref **refs, int nr_match, char **match) } } - for_each_ref(mark_complete, NULL); - if (cutoff) - mark_recent_complete_commits(cutoff); + if (!depth) { + for_each_ref(mark_complete, NULL); + if (cutoff) + mark_recent_complete_commits(cutoff); + } /* * Mark all complete remote refs as common refs. @@ -522,6 +557,8 @@ static int fetch_pack(int fd[2], int nr_match, char **match) int status; get_remote_heads(fd[0], &ref, 0, NULL, 0); + if (is_repository_shallow() && !server_supports("shallow")) + die("Server does not support shallow clients"); if (server_supports("multi_ack")) { if (verbose) fprintf(stderr, "Server supports multi_ack\n"); @@ -594,6 +631,8 @@ int main(int argc, char **argv) char *dest = NULL, **heads; int fd[2]; pid_t pid; + struct stat st; + struct lock_file lock; setup_git_directory(); @@ -627,6 +666,12 @@ int main(int argc, char **argv) verbose = 1; continue; } + if (!strncmp("--depth=", arg, 8)) { + depth = strtol(arg + 8, NULL, 0); + if (stat(git_path("shallow"), &st)) + st.st_mtime = 0; + continue; + } usage(fetch_pack_usage); } dest = arg; @@ -659,5 +704,34 @@ int main(int argc, char **argv) } } + if (!ret && depth > 0) { + struct cache_time mtime; + char *shallow = git_path("shallow"); + int fd; + + mtime.sec = st.st_mtime; +#ifdef USE_NSEC + mtime.usec = st.st_mtim.usec; +#endif + if (stat(shallow, &st)) { + if (mtime.sec) + die("shallow file was removed during fetch"); + } else if (st.st_mtime != mtime.sec +#ifdef USE_NSEC + || st.st_mtim.usec != mtime.usec +#endif + ) + die("shallow file was changed during fetch"); + + fd = hold_lock_file_for_update(&lock, shallow, 1); + if (!write_shallow_commits(fd, 0)) { + unlink(shallow); + rollback_lock_file(&lock); + } else { + close(fd); + commit_lock_file(&lock); + } + } + return !!ret; } @@ -6,6 +6,7 @@ USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] [--interactive] [--whitespace=<option>] <mbox>... or, when resuming [--skip | --resolved]' . git-sh-setup +set_reflog_action am git var GIT_COMMITTER_IDENT >/dev/null || exit @@ -101,7 +102,6 @@ It does not apply to blobs recorded in its index." } prec=4 -rloga=am dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= resolvemsg= while case "$#" in 0) break;; esac @@ -141,9 +141,6 @@ do --resolvemsg=*) resolvemsg=$(echo "$1" | sed -e "s/^--resolvemsg=//"); shift ;; - --reflog-action=*) - rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'`; shift ;; - --) shift; break ;; -*) @@ -452,7 +449,7 @@ do parent=$(git-rev-parse --verify HEAD) && commit=$(git-commit-tree $tree -p $parent <"$dotest/final-commit") && echo Committed: $commit && - git-update-ref -m "$rloga: $SUBJECT" HEAD $commit $parent || + git-update-ref -m "$GIT_REFLOG_ACTION: $SUBJECT" HEAD $commit $parent || stop_here $this if test -x "$GIT_DIR"/hooks/post-applypatch diff --git a/git-clone.sh b/git-clone.sh index 490f3e48db..3d388de62a 100755 --- a/git-clone.sh +++ b/git-clone.sh @@ -14,7 +14,7 @@ die() { } usage() { - die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [-n] <repo> [<dir>]" + die "Usage: $0 [--template=<template_directory>] [--reference <reference-repo>] [--bare] [-l [-s]] [-q] [-u <upload-pack>] [--origin <name>] [--depth <n>] [-n] <repo> [<dir>]" } get_repo_base() { @@ -120,6 +120,7 @@ reference= origin= origin_override= use_separate_remote=t +depth= while case "$#,$1" in 0,*) break ;; @@ -163,6 +164,10 @@ while *,-u|*,--upload-pack) shift upload_pack="--exec=$1" ;; + 1,--depth) usage;; + *,--depth) + shift + depth="--depth=$1";; *,-*) usage ;; *) break ;; esac @@ -267,6 +272,10 @@ yes,yes) *) case "$repo" in rsync://*) + case "$depth" in + "") ;; + *) die "shallow over rsync not supported" ;; + esac rsync $quiet -av --ignore-existing \ --exclude info "$repo/objects/" "$GIT_DIR/objects/" || exit @@ -295,6 +304,10 @@ yes,yes) git-ls-remote "$repo" >"$GIT_DIR/CLONE_HEAD" || exit 1 ;; https://*|http://*|ftp://*) + case "$depth" in + "") ;; + *) die "shallow over http or ftp not supported" ;; + esac if test -z "@@NO_CURL@@" then clone_dumb_http "$repo" "$D" @@ -304,8 +317,8 @@ yes,yes) ;; *) case "$upload_pack" in - '') git-fetch-pack --all -k $quiet "$repo" ;; - *) git-fetch-pack --all -k $quiet "$upload_pack" "$repo" ;; + '') git-fetch-pack --all -k $quiet $depth "$repo" ;; + *) git-fetch-pack --all -k $quiet "$upload_pack" $depth "$repo" ;; esac >"$GIT_DIR/CLONE_HEAD" || die "fetch-pack from '$repo' failed." ;; @@ -375,7 +388,7 @@ then # Set up the mappings to track the remote branches. git-repo-config remote."$origin".fetch \ - "refs/heads/*:$remote_top/*" '^$' && + "+refs/heads/*:$remote_top/*" '^$' && rm -f "refs/remotes/$origin/HEAD" git-symbolic-ref "refs/remotes/$origin/HEAD" \ "refs/remotes/$origin/$head_points_at" && diff --git a/git-fetch.sh b/git-fetch.sh index ffbd44f0e1..8bd11f8b60 100755 --- a/git-fetch.sh +++ b/git-fetch.sh @@ -4,6 +4,8 @@ USAGE='<fetch-options> <repository> <refspec>...' SUBDIRECTORY_OK=Yes . git-sh-setup +set_reflog_action "fetch $*" + TOP=$(git-rev-parse --show-cdup) if test ! -z "$TOP" then @@ -17,7 +19,6 @@ LF=' ' IFS="$LF" -rloga=fetch no_tags= tags= append= @@ -27,6 +28,7 @@ update_head_ok= exec= upload_pack= keep= +shallow_depth= while case "$#" in 0) break ;; esac do case "$1" in @@ -59,8 +61,12 @@ do -k|--k|--ke|--kee|--keep) keep='-k -k' ;; - --reflog-action=*) - rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` + --depth=*) + shallow_depth="--depth=`expr "z$1" : 'z-[^=]*=\(.*\)'`" + ;; + --depth) + shift + shallow_depth="--depth=$1" ;; -*) usage @@ -86,9 +92,6 @@ refs= rref= rsync_slurped_objects= -rloga="$rloga $remote_nick" -test "$remote_nick" = "$remote" || rloga="$rloga $remote" - if test "" = "$append" then : >"$GIT_DIR/FETCH_HEAD" @@ -172,12 +175,12 @@ update_local_ref () { else echo >&2 "* $1: updating with $3" echo >&2 " $label_: $newshort_" - git-update-ref -m "$rloga: updating tag" "$1" "$2" + git-update-ref -m "$GIT_REFLOG_ACTION: updating tag" "$1" "$2" fi else echo >&2 "* $1: storing $3" echo >&2 " $label_: $newshort_" - git-update-ref -m "$rloga: storing tag" "$1" "$2" + git-update-ref -m "$GIT_REFLOG_ACTION: storing tag" "$1" "$2" fi ;; @@ -200,7 +203,7 @@ update_local_ref () { *,$local) echo >&2 "* $1: fast forward to $3" echo >&2 " old..new: $oldshort_..$newshort_" - git-update-ref -m "$rloga: fast-forward" "$1" "$2" "$local" + git-update-ref -m "$GIT_REFLOG_ACTION: fast-forward" "$1" "$2" "$local" ;; *) false @@ -210,7 +213,7 @@ update_local_ref () { *,t,*) echo >&2 "* $1: forcing update to non-fast forward $3" echo >&2 " old...new: $oldshort_...$newshort_" - git-update-ref -m "$rloga: forced-update" "$1" "$2" "$local" + git-update-ref -m "$GIT_REFLOG_ACTION: forced-update" "$1" "$2" "$local" ;; *) echo >&2 "* $1: not updating to non-fast forward $3" @@ -222,7 +225,7 @@ update_local_ref () { else echo >&2 "* $1: storing $3" echo >&2 " $label_: $newshort_" - git-update-ref -m "$rloga: storing head" "$1" "$2" + git-update-ref -m "$GIT_REFLOG_ACTION: storing head" "$1" "$2" fi ;; esac @@ -305,6 +308,8 @@ fetch_main () { # There are transports that can fetch only one head at a time... case "$remote" in http://* | https://* | ftp://*) + test -n "$shallow_depth" && + die "shallow clone with http not supported" proto=`expr "$remote" : '\([^:]*\):'` if [ -n "$GIT_SSL_NO_VERIFY" ]; then curl_extra_args="-k" @@ -331,6 +336,8 @@ fetch_main () { git-http-fetch -v -a "$head" "$remote/" || exit ;; rsync://*) + test -n "$shallow_depth" && + die "shallow clone with rsync not supported" TMP_HEAD="$GIT_DIR/TMP_HEAD" rsync -L -q "$remote/$remote_name" "$TMP_HEAD" || exit 1 head=$(git-rev-parse --verify TMP_HEAD) @@ -378,7 +385,7 @@ fetch_main () { pack_lockfile= IFS=" $LF" ( - git-fetch-pack --thin $exec $keep "$remote" $rref || echo failed "$remote" + git-fetch-pack --thin $exec $keep $shallow_depth "$remote" $rref || echo failed "$remote" ) | while read sha1 remote_name do @@ -451,6 +458,8 @@ case "$no_tags$tags" in case "$taglist" in '') ;; ?*) + # do not deepen a shallow tree when following tags + shallow_depth= fetch_main "$taglist" || exit ;; esac esac @@ -465,7 +474,7 @@ case "$orig_head" in if test "$curr_head" != "$orig_head" then git-update-ref \ - -m "$rloga: Undoing incorrectly fetched HEAD." \ + -m "$GIT_REFLOG_ACTION: Undoing incorrectly fetched HEAD." \ HEAD "$orig_head" die "Cannot fetch into the current branch." fi diff --git a/git-merge.sh b/git-merge.sh index 7dd0a11236..ba42260426 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -3,9 +3,10 @@ # Copyright (c) 2005 Junio C Hamano # -USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [--reflog-action=<action>] [-m=<merge-message>] <commit>+' +USAGE='[-n] [--no-commit] [--squash] [-s <strategy>] [-m=<merge-message>] <commit>+' . git-sh-setup +set_reflog_action "merge $*" LF=' ' @@ -57,10 +58,10 @@ squash_message () { finish () { if test '' = "$2" then - rlogm="$rloga" + rlogm="$GIT_REFLOG_ACTION" else echo "$2" - rlogm="$rloga: $2" + rlogm="$GIT_REFLOG_ACTION: $2" fi case "$squash" in t) @@ -109,7 +110,7 @@ merge_name () { case "$#" in 0) usage ;; esac -rloga= have_message= +have_message= while case "$#" in 0) break ;; esac do case "$1" in @@ -139,9 +140,6 @@ do die "available strategies are: $all_strategies" ;; esac ;; - --reflog-action=*) - rloga=`expr "z$1" : 'z-[^=]*=\(.*\)'` - ;; -m=*|--m=*|--me=*|--mes=*|--mess=*|--messa=*|--messag=*|--message=*) merge_msg=`expr "z$1" : 'z-[^=]*=\(.*\)'` have_message=t @@ -213,7 +211,6 @@ head=$(git-rev-parse --verify "$head_arg"^0) || usage # All the rest are remote heads test "$#" = 0 && usage ;# we need at least one remote head. -test "$rloga" = '' && rloga="merge: $@" remoteheads= for remote @@ -230,9 +227,21 @@ case "$use_strategies" in '') case "$#" in 1) - use_strategies="$default_twohead_strategies" ;; + var="`git-repo-config --get pull.twohead`" + if test -n "$var" + then + use_strategies="$var" + else + use_strategies="$default_twohead_strategies" + fi ;; *) - use_strategies="$default_octopus_strategies" ;; + var="`git-repo-config --get pull.octopus`" + if test -n "$var" + then + use_strategies="$var" + else + use_strategies="$default_octopus_strategies" + fi ;; esac ;; esac diff --git a/git-pull.sh b/git-pull.sh index 1703091bbb..28d08195f0 100755 --- a/git-pull.sh +++ b/git-pull.sh @@ -7,6 +7,7 @@ USAGE='[-n | --no-summary] [--no-commit] [-s strategy]... [<fetch-options>] <repo> <head>...' LONG_USAGE='Fetch one or more remote refs and merge it/them into the current HEAD.' . git-sh-setup +set_reflog_action "pull $*" strategy_args= no_summary= no_commit= squash= while case "$#,$1" in 0) break ;; *,-*) ;; *) break ;; esac @@ -45,7 +46,7 @@ do done orig_head=$(git-rev-parse --verify HEAD 2>/dev/null) -git-fetch --update-head-ok --reflog-action=pull "$@" || exit 1 +git-fetch --update-head-ok "$@" || exit 1 curr_head=$(git-rev-parse --verify HEAD 2>/dev/null) if test "$curr_head" != "$orig_head" @@ -89,18 +90,6 @@ case "$merge_head" in echo >&2 "Cannot merge multiple branches into empty head" exit 1 fi - var=`git-repo-config --get pull.octopus` - if test -n "$var" - then - strategy_default_args="-s $var" - fi - ;; -*) - var=`git-repo-config --get pull.twohead` - if test -n "$var" - then - strategy_default_args="-s $var" - fi ;; esac @@ -111,13 +100,6 @@ then exit fi -case "$strategy_args" in -'') - strategy_args=$strategy_default_args - ;; -esac - merge_name=$(git-fmt-merge-msg <"$GIT_DIR/FETCH_HEAD") || exit -git-merge "--reflog-action=pull $*" \ - $no_summary $no_commit $squash $strategy_args \ +exec git-merge $no_summary $no_commit $squash $strategy_args \ "$merge_name" HEAD $merge_head diff --git a/git-rebase.sh b/git-rebase.sh index ece31425d0..828c59ce61 100755 --- a/git-rebase.sh +++ b/git-rebase.sh @@ -28,6 +28,7 @@ Example: git-rebase master~1 topic D---E---F---G master D---E---F---G master ' . git-sh-setup +set_reflog_action rebase RESOLVEMSG=" When you have resolved this problem run \"git rebase --continue\". @@ -80,10 +81,18 @@ continue_merge () { call_merge () { cmt="$(cat $dotest/cmt.$1)" echo "$cmt" > "$dotest/current" - git-merge-$strategy "$cmt^" -- HEAD "$cmt" + hd=$(git-rev-parse --verify HEAD) + cmt_name=$(git-symbolic-ref HEAD) + msgnum=$(cat $dotest/msgnum) + end=$(cat $dotest/end) + eval GITHEAD_$cmt='"${cmt_name##refs/heads/}~$(($end - $msgnum))"' + eval GITHEAD_$hd='"$(cat $dotest/onto_name)"' + export GITHEAD_$cmt GITHEAD_$hd + git-merge-$strategy "$cmt^" -- "$hd" "$cmt" rv=$? case "$rv" in 0) + unset GITHEAD_$cmt GITHEAD_$hd return ;; 1) @@ -132,8 +141,7 @@ do finish_rb_merge exit fi - git am --resolved --3way --resolvemsg="$RESOLVEMSG" \ - --reflog-action=rebase + git am --resolved --3way --resolvemsg="$RESOLVEMSG" exit ;; --skip) @@ -156,8 +164,7 @@ do finish_rb_merge exit fi - git am -3 --skip --resolvemsg="$RESOLVEMSG" \ - --reflog-action=rebase + git am -3 --skip --resolvemsg="$RESOLVEMSG" exit ;; --abort) @@ -306,8 +313,7 @@ fi if test -z "$do_merge" then git-format-patch -k --stdout --full-index --ignore-if-in-upstream "$upstream"..ORIG_HEAD | - git am --binary -3 -k --resolvemsg="$RESOLVEMSG" \ - --reflog-action=rebase + git am --binary -3 -k --resolvemsg="$RESOLVEMSG" exit $? fi @@ -316,6 +322,7 @@ fi mkdir -p "$dotest" echo "$onto" > "$dotest/onto" +echo "$onto_name" > "$dotest/onto_name" prev_head=`git-rev-parse HEAD^0` echo "$prev_head" > "$dotest/prev_head" diff --git a/git-reset.sh b/git-reset.sh index 2379db082f..a9693701a3 100755 --- a/git-reset.sh +++ b/git-reset.sh @@ -5,6 +5,7 @@ USAGE='[--mixed | --soft | --hard] [<commit-ish>] [ [--] <paths>...]' SUBDIRECTORY_OK=Yes . git-sh-setup +set_reflog_action "reset $*" update= reset_type=--mixed unset rev @@ -81,7 +82,7 @@ then else rm -f "$GIT_DIR/ORIG_HEAD" fi -git-update-ref -m "reset $reset_type $*" HEAD "$rev" +git-update-ref -m "$GIT_REFLOG_ACTION" HEAD "$rev" update_ref_status=$? case "$reset_type" in diff --git a/git-sh-setup.sh b/git-sh-setup.sh index 42f9b1c125..87b939c0e4 100755 --- a/git-sh-setup.sh +++ b/git-sh-setup.sh @@ -20,6 +20,14 @@ usage() { die "Usage: $0 $USAGE" } +set_reflog_action() { + if [ -z "${GIT_REFLOG_ACTION:+set}" ] + then + GIT_REFLOG_ACTION="$*" + export GIT_REFLOG_ACTION + fi +} + if [ -z "$LONG_USAGE" ] then LONG_USAGE="Usage: $0 $USAGE" diff --git a/git-svn.perl b/git-svn.perl index c2cdceb1d1..b28c5bbc72 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -69,7 +69,7 @@ my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, $_limit, $_verbose, $_incremental, $_oneline, $_l_fmt, $_show_commit, $_version, $_upgrade, $_authors, $_branch_all_refs, @_opt_m, $_merge, $_strategy, $_dry_run, $_ignore_nodate, $_non_recursive, - $_username, $_config_dir, $_no_auth_cache, $_xfer_delta, + $_username, $_config_dir, $_no_auth_cache, $_pager, $_color); my (@_branch_from, %tree_map, %users, %rusers, %equiv); my ($_svn_can_do_switch); @@ -216,7 +216,7 @@ information. } sub version { - print "git-svn version $VERSION\n"; + print "git-svn version $VERSION (svn $SVN::Core::VERSION)\n"; exit 0; } @@ -1098,7 +1098,8 @@ sub read_uuid { sub verify_ref { my ($ref) = @_; - eval { command_oneline([ 'rev-parse', $ref ], { STDERR => 0 }) }; + eval { command_oneline([ 'rev-parse', '--verify', $ref ], + { STDERR => 0 }); }; } sub repo_path_split { @@ -2044,13 +2045,6 @@ sub libsvn_connect { config => $config, pool => SVN::Pool->new, auth_provider_callbacks => $callbacks); - - my $df = $ENV{GIT_SVN_DELTA_FETCH}; - if (defined $df) { - $_xfer_delta = $df; - } else { - $_xfer_delta = ($url =~ m#^file://#) ? undef : 1; - } $ra->{svn_path} = $url; $ra->{repos_root} = $ra->get_repos_root; $ra->{svn_path} =~ s#^\Q$ra->{repos_root}\E/*##; @@ -2082,49 +2076,6 @@ sub libsvn_dup_ra { auth auth_provider_callbacks repos_root svn_path/); } -sub libsvn_get_file { - my ($gui, $f, $rev, $chg, $untracked) = @_; - $f =~ s#^/##; - print "\t$chg\t$f\n" unless $_q; - - my ($hash, $pid, $in, $out); - my $pool = SVN::Pool->new; - defined($pid = open3($in, $out, '>&STDERR', - qw/git-hash-object -w --stdin/)) or croak $!; - # redirect STDOUT for SVN 1.1.x compatibility - open my $stdout, '>&', \*STDOUT or croak $!; - open STDOUT, '>&', $in or croak $!; - my ($r, $props) = $SVN->get_file($f, $rev, \*STDOUT, $pool); - $in->flush == 0 or croak $!; - open STDOUT, '>&', $stdout or croak $!; - close $in or croak $!; - close $stdout or croak $!; - $pool->clear; - chomp($hash = do { local $/; <$out> }); - close $out or croak $!; - waitpid $pid, 0; - $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; - - my $mode = exists $props->{'svn:executable'} ? '100755' : '100644'; - if (exists $props->{'svn:special'}) { - $mode = '120000'; - my $link = `git-cat-file blob $hash`; # no chomping symlinks - $link =~ s/^link // or die "svn:special file with contents: <", - $link, "> is not understood\n"; - defined($pid = open3($in, $out, '>&STDERR', - qw/git-hash-object -w --stdin/)) or croak $!; - print $in $link; - $in->flush == 0 or croak $!; - close $in or croak $!; - chomp($hash = do { local $/; <$out> }); - close $out or croak $!; - waitpid $pid, 0; - $hash =~ /^$sha1$/o or die "not a sha1: $hash\n"; - } - %{$untracked->{file_prop}->{$f}} = %$props; - print $gui $mode,' ',$hash,"\t",$f,"\0" or croak $!; -} - sub uri_encode { my ($f) = @_; $f =~ s#([^a-zA-Z0-9\*!\:_\./\-])#uc sprintf("%%%02x",ord($1))#eg; @@ -2232,10 +2183,6 @@ sub process_rm { } sub libsvn_fetch { - $_xfer_delta ? libsvn_fetch_delta(@_) : libsvn_fetch_full(@_); -} - -sub libsvn_fetch_delta { my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; my $pool = SVN::Pool->new; my $ed = SVN::Git::Fetcher->new({ c => $last_commit, q => $_q }); @@ -2251,66 +2198,6 @@ sub libsvn_fetch_delta { libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ed); } -sub libsvn_fetch_full { - my ($last_commit, $paths, $rev, $author, $date, $msg) = @_; - my ($gui, $ctx) = command_input_pipe(qw/update-index -z --index-info/); - my %amr; - my $ut = { empty => {}, dir_prop => {}, file_prop => {} }; - my $p = $SVN->{svn_path}; - foreach my $f (keys %$paths) { - my $m = $paths->{$f}->action(); - if (length $p) { - $f =~ s#^/\Q$p\E/##; - next if $f =~ m#^/#; - } else { - $f =~ s#^/##; - } - if ($m =~ /^[DR]$/) { - my $t = process_rm($gui, $last_commit, $f, $_q); - if ($m eq 'D') { - $ut->{empty}->{$f} = 0 if $t == $SVN::Node::dir; - next; - } - # 'R' can be file replacements, too, right? - } - my $pool = SVN::Pool->new; - my $t = $SVN->check_path($f, $rev, $pool); - if ($t == $SVN::Node::file) { - if ($m =~ /^[AMR]$/) { - $amr{$f} = $m; - } else { - die "Unrecognized action: $m, ($f r$rev)\n"; - } - } elsif ($t == $SVN::Node::dir && $m =~ /^[AR]$/) { - my @traversed = (); - libsvn_traverse($gui, '', $f, $rev, \@traversed, $ut); - if (@traversed) { - foreach (@traversed) { - $amr{$_} = $m; - } - } else { - my ($dir, $file) = ($f =~ m#^(.*?)/?([^/]+)$#); - delete $ut->{empty}->{$dir}; - $ut->{empty}->{$f} = 1; - } - } - $pool->clear; - } - foreach (keys %amr) { - libsvn_get_file($gui, $_, $rev, $amr{$_}, $ut); - my ($d) = ($_ =~ m#^(.*?)/?(?:[^/]+)$#); - delete $ut->{empty}->{$d}; - } - unless (exists $ut->{dir_prop}->{''}) { - my $pool = SVN::Pool->new; - my (undef, undef, $props) = $SVN->get_dir('', $rev, $pool); - %{$ut->{dir_prop}->{''}} = %$props; - $pool->clear; - } - command_close_pipe($gui, $ctx); - libsvn_log_entry($rev, $author, $date, $msg, [$last_commit], $ut); -} - sub svn_grab_base_rev { my $c = eval { command_oneline([qw/rev-parse --verify/, "refs/remotes/$GIT_SVN^0"], @@ -2362,41 +2249,6 @@ sub libsvn_parse_revision { "Try using the command-line svn client instead\n"; } -sub libsvn_traverse { - my ($gui, $pfx, $path, $rev, $files, $untracked) = @_; - my $cwd = length $pfx ? "$pfx/$path" : $path; - my $pool = SVN::Pool->new; - $cwd =~ s#^\Q$SVN->{svn_path}\E##; - my $nr = 0; - my ($dirent, $r, $props) = $SVN->get_dir($cwd, $rev, $pool); - %{$untracked->{dir_prop}->{$cwd}} = %$props; - foreach my $d (keys %$dirent) { - my $t = $dirent->{$d}->kind; - if ($t == $SVN::Node::dir) { - my $i = libsvn_traverse($gui, $cwd, $d, $rev, - $files, $untracked); - if ($i) { - $nr += $i; - } else { - $untracked->{empty}->{"$cwd/$d"} = 1; - } - } elsif ($t == $SVN::Node::file) { - $nr++; - my $file = "$cwd/$d"; - if (defined $files) { - push @$files, $file; - } else { - libsvn_get_file($gui, $file, $rev, 'A', - $untracked); - my ($dir) = ($file =~ m#^(.*?)/?(?:[^/]+)$#); - delete $untracked->{empty}->{$dir}; - } - } - } - $pool->clear; - $nr; -} - sub libsvn_traverse_ignore { my ($fh, $path, $r) = @_; $path =~ s#^/+##g; @@ -2488,8 +2340,8 @@ sub libsvn_find_parent_branch { print STDERR "Found branch parent: ($GIT_SVN) $parent\n"; command_noisy('read-tree', $parent); unless (libsvn_can_do_switch()) { - return libsvn_fetch_full($parent, $paths, $rev, - $author, $date, $msg); + return _libsvn_new_tree($paths, $rev, $author, $date, + $msg, [$parent]); } # do_switch works with svn/trunk >= r22312, but that is not # included with SVN 1.4.2 (the latest version at the moment), @@ -2514,7 +2366,7 @@ sub libsvn_find_parent_branch { sub libsvn_get_log { my ($ra, @args) = @_; - $args[4]-- if $args[4] && $_xfer_delta && ! $_follow_parent; + $args[4]-- if $args[4] && ! $_follow_parent; if ($SVN::Core::VERSION le '1.2.0') { splice(@args, 3, 1); } @@ -2525,28 +2377,23 @@ sub libsvn_new_tree { if (my $log_entry = libsvn_find_parent_branch(@_)) { return $log_entry; } - my ($paths, $rev, $author, $date, $msg) = @_; - my $ut; - if ($_xfer_delta) { - my $pool = SVN::Pool->new; - my $ed = SVN::Git::Fetcher->new({q => $_q}); - my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); - my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); - $reporter->set_path('', $rev, 1, @lock, $pool); - $reporter->finish_report($pool); - $pool->clear; - unless ($ed->{git_commit_ok}) { - die "SVN connection failed somewhere...\n"; - } - $ut = $ed; - } else { - $ut = { empty => {}, dir_prop => {}, file_prop => {} }; - my ($gui, $ctx) = command_input_pipe(qw/update-index - -z --index-info/); - libsvn_traverse($gui, '', $SVN->{svn_path}, $rev, undef, $ut); - command_close_pipe($gui, $ctx); + my ($paths, $rev, $author, $date, $msg) = @_; # $pool is last + _libsvn_new_tree($paths, $rev, $author, $date, $msg, []); +} + +sub _libsvn_new_tree { + my ($paths, $rev, $author, $date, $msg, $parents) = @_; + my $pool = SVN::Pool->new; + my $ed = SVN::Git::Fetcher->new({q => $_q}); + my $reporter = $SVN->do_update($rev, '', 1, $ed, $pool); + my @lock = $SVN::Core::VERSION ge '1.2.0' ? (undef) : (); + $reporter->set_path('', $rev, 1, @lock, $pool); + $reporter->finish_report($pool); + $pool->clear; + unless ($ed->{git_commit_ok}) { + die "SVN connection failed somewhere...\n"; } - libsvn_log_entry($rev, $author, $date, $msg, [], $ut); + libsvn_log_entry($rev, $author, $date, $msg, $parents, $ed); } sub find_graft_path_commit { @@ -2634,7 +2481,7 @@ sub libsvn_ls_fullurl { my $pool = SVN::Pool->new; my $r = defined $_revision ? $_revision : $ra->get_latest_revnum; my ($dirent, undef, undef) = $ra->get_dir('', $r, $pool); - foreach my $d (keys %$dirent) { + foreach my $d (sort keys %$dirent) { if ($dirent->{$d}->kind == $SVN::Node::dir) { push @ret, "$d/"; # add '/' for compat with cli svn } diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index da12be7472..d845e91e20 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -18,6 +18,10 @@ use File::Find qw(); use File::Basename qw(basename); binmode STDOUT, ':utf8'; +BEGIN { + CGI->compile() if $ENV{MOD_PERL}; +} + our $cgi = new CGI; our $version = "++GIT_VERSION++"; our $my_url = $cgi->url(); @@ -1711,6 +1715,7 @@ sub git_header_html { } print $cgi->header(-type=>$content_type, -charset => 'utf-8', -status=> $status, -expires => $expires); + my $mod_perl_version = $ENV{'MOD_PERL'} ? " $ENV{'MOD_PERL'}" : ''; print <<EOF; <?xml version="1.0" encoding="utf-8"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> @@ -1719,7 +1724,7 @@ sub git_header_html { <!-- git core binaries version $git_version --> <head> <meta http-equiv="content-type" content="$content_type; charset=utf-8"/> -<meta name="generator" content="gitweb/$version git/$git_version"/> +<meta name="generator" content="gitweb/$version git/$git_version$mod_perl_version"/> <meta name="robots" content="index, nofollow"/> <title>$title</title> EOF @@ -925,7 +925,8 @@ static int log_ref_write(struct ref_lock *lock, const char *committer; if (log_all_ref_updates && - !strncmp(lock->ref_name, "refs/heads/", 11)) { + (!strncmp(lock->ref_name, "refs/heads/", 11) || + !strncmp(lock->ref_name, "refs/remotes/", 13))) { if (safe_create_leading_directories(lock->log_file) < 0) return error("unable to create directory for %s", lock->log_file); diff --git a/revision.h b/revision.h index ec991e5c57..8f7907d7ab 100644 --- a/revision.h +++ b/revision.h @@ -72,6 +72,7 @@ struct rev_info { const char *ref_message_id; const char *add_signoff; const char *extra_headers; + const char *log_reencode; /* Filter by commit log message */ struct grep_opt *grep_filter; diff --git a/shallow.c b/shallow.c new file mode 100644 index 0000000000..3d53d17423 --- /dev/null +++ b/shallow.c @@ -0,0 +1,104 @@ +#include "cache.h" +#include "commit.h" +#include "tag.h" + +static int is_shallow = -1; + +int register_shallow(const unsigned char *sha1) +{ + struct commit_graft *graft = + xmalloc(sizeof(struct commit_graft)); + struct commit *commit = lookup_commit(sha1); + + hashcpy(graft->sha1, sha1); + graft->nr_parent = -1; + if (commit && commit->object.parsed) + commit->parents = NULL; + return register_commit_graft(graft, 0); +} + +int is_repository_shallow() +{ + FILE *fp; + char buf[1024]; + + if (is_shallow >= 0) + return is_shallow; + + fp = fopen(git_path("shallow"), "r"); + if (!fp) { + is_shallow = 0; + return is_shallow; + } + is_shallow = 1; + + while (fgets(buf, sizeof(buf), fp)) { + unsigned char sha1[20]; + if (get_sha1_hex(buf, sha1)) + die("bad shallow line: %s", buf); + register_shallow(sha1); + } + fclose(fp); + return is_shallow; +} + +struct commit_list *get_shallow_commits(struct object_array *heads, int depth, + int shallow_flag, int not_shallow_flag) +{ + int i = 0, cur_depth = 0; + struct commit_list *result = NULL; + struct object_array stack = {0, 0, NULL}; + struct commit *commit = NULL; + + while (commit || i < heads->nr || stack.nr) { + struct commit_list *p; + if (!commit) { + if (i < heads->nr) { + commit = (struct commit *) + deref_tag(heads->objects[i++].item, NULL, 0); + if (commit->object.type != OBJ_COMMIT) { + commit = NULL; + continue; + } + if (!commit->util) + commit->util = xmalloc(sizeof(int)); + *(int *)commit->util = 0; + cur_depth = 0; + } else { + commit = (struct commit *) + stack.objects[--stack.nr].item; + cur_depth = *(int *)commit->util; + } + } + parse_commit(commit); + commit->object.flags |= not_shallow_flag; + cur_depth++; + for (p = commit->parents, commit = NULL; p; p = p->next) { + if (!p->item->util) { + int *pointer = xmalloc(sizeof(int)); + p->item->util = pointer; + *pointer = cur_depth; + } else { + int *pointer = p->item->util; + if (cur_depth >= *pointer) + continue; + *pointer = cur_depth; + } + if (cur_depth < depth) { + if (p->next) + add_object_array(&p->item->object, + NULL, &stack); + else { + commit = p->item; + cur_depth = *(int *)commit->util; + } + } else { + commit_list_insert(p->item, &result); + p->item->object.flags |= shallow_flag; + } + } + } + + return result; +} + diff --git a/t/Makefile b/t/Makefile index 250a19019c..19e38508a7 100644 --- a/t/Makefile +++ b/t/Makefile @@ -23,8 +23,7 @@ clean: # we can test NO_OPTIMIZE_COMMITS independently of LC_ALL full-svn-test: - $(MAKE) $(TSVN) GIT_SVN_DELTA_FETCH=1 \ - GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C + $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=1 LC_ALL=C $(MAKE) $(TSVN) GIT_SVN_NO_OPTIMIZE_COMMITS=0 LC_ALL=en_US.UTF-8 .PHONY: $(T) clean @@ -74,6 +74,8 @@ First digit tells the family: 5 - the pull and exporting commands 6 - the revision tree commands (even e.g. merge-base) 7 - the porcelainish commands concerning the working tree + 8 - the porcelainish commands concerning forensics + 9 - the git tools Second digit tells the particular command we are testing. diff --git a/t/t3700-add.sh b/t/t3700-add.sh index c09c53f20b..e98786de32 100755 --- a/t/t3700-add.sh +++ b/t/t3700-add.sh @@ -51,4 +51,37 @@ test_expect_success \ *) echo fail; git-ls-files --stage xfoo3; (exit 1);; esac' +test_expect_success '.gitignore test setup' ' + echo "*.ig" >.gitignore && + mkdir c.if d.ig && + >a.ig && >b.if && + >c.if/c.if && >c.if/c.ig && + >d.ig/d.if && >d.ig/d.ig +' + +test_expect_success '.gitignore is honored' ' + git-add . && + ! git-ls-files | grep "\\.ig" +' + +test_expect_success 'error out when attempting to add ignored ones without -f' ' + ! git-add a.?? && + ! git-ls-files | grep "\\.ig" +' + +test_expect_success 'error out when attempting to add ignored ones without -f' ' + ! git-add d.?? && + ! git-ls-files | grep "\\.ig" +' + +test_expect_success 'add ignored ones with -f' ' + git-add -f a.?? && + git-ls-files --error-unmatch a.ig +' + +test_expect_success 'add ignored ones with -f' ' + git-add -f d.??/* && + git-ls-files --error-unmatch d.ig/d.if d.ig/d.ig +' + test_done diff --git a/t/t3900-i18n-commit.sh b/t/t3900-i18n-commit.sh new file mode 100755 index 0000000000..46fd47cb0f --- /dev/null +++ b/t/t3900-i18n-commit.sh @@ -0,0 +1,115 @@ +#!/bin/sh +# +# Copyright (c) 2006 Junio C Hamano +# + +test_description='commit and log output encodings' + +. ./test-lib.sh + +compare_with () { + git-show -s "$1" | sed -e '1,/^$/d' -e 's/^ //' -e '$d' >current && + diff -u current "$2" +} + +test_expect_success setup ' + : >F && + git-add F && + T=$(git-write-tree) && + C=$(git-commit-tree $T <../t3900/1-UTF-8.txt) && + git-update-ref HEAD $C && + git-tag C0 +' + +test_expect_success 'no encoding header for base case' ' + E=$(git-cat-file commit C0 | sed -ne "s/^encoding //p") && + test z = "z$E" +' + +for H in ISO-8859-1 EUCJP ISO-2022-JP +do + test_expect_success "$H setup" ' + git-repo-config i18n.commitencoding $H && + git-checkout -b $H C0 && + echo $H >F && + git-commit -a -F ../t3900/$H.txt + ' +done + +for H in ISO-8859-1 EUCJP ISO-2022-JP +do + test_expect_success "check encoding header for $H" ' + E=$(git-cat-file commit '$H' | sed -ne "s/^encoding //p") && + test "z$E" = "z'$H'" + ' +done + +test_expect_success 'repo-config to remove customization' ' + git-repo-config --unset-all i18n.commitencoding && + if Z=$(git-repo-config --get-all i18n.commitencoding) + then + echo Oops, should have failed. + false + else + test z = "z$Z" + fi && + git-repo-config i18n.commitencoding utf-8 +' + +test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' ' + compare_with ISO-8859-1 ../t3900/1-UTF-8.txt +' + +for H in EUCJP ISO-2022-JP +do + test_expect_success "$H should be shown in UTF-8 now" ' + compare_with '$H' ../t3900/2-UTF-8.txt + ' +done + +test_expect_success 'repo-config to add customization' ' + git-repo-config --unset-all i18n.commitencoding && + if Z=$(git-repo-config --get-all i18n.commitencoding) + then + echo Oops, should have failed. + false + else + test z = "z$Z" + fi +' + +for H in ISO-8859-1 EUCJP ISO-2022-JP +do + test_expect_success "$H should be shown in itself now" ' + git-repo-config i18n.commitencoding '$H' && + compare_with '$H' ../t3900/'$H'.txt + ' +done + +test_expect_success 'repo-config to tweak customization' ' + git-repo-config i18n.logoutputencoding utf-8 +' + +test_expect_success 'ISO-8859-1 should be shown in UTF-8 now' ' + compare_with ISO-8859-1 ../t3900/1-UTF-8.txt +' + +for H in EUCJP ISO-2022-JP +do + test_expect_success "$H should be shown in UTF-8 now" ' + compare_with '$H' ../t3900/2-UTF-8.txt + ' +done + +for J in EUCJP ISO-2022-JP +do + git-repo-config i18n.logoutputencoding $J + for H in EUCJP ISO-2022-JP + do + test_expect_success "$H should be shown in $J now" ' + compare_with '$H' ../t3900/'$J'.txt + ' + done +done + +test_done diff --git a/t/t3900/1-UTF-8.txt b/t/t3900/1-UTF-8.txt new file mode 100644 index 0000000000..ee31e19738 --- /dev/null +++ b/t/t3900/1-UTF-8.txt @@ -0,0 +1,3 @@ +ÄËÑÏÖ + +Ábçdèfg diff --git a/t/t3900/2-UTF-8.txt b/t/t3900/2-UTF-8.txt new file mode 100644 index 0000000000..63f4f8f121 --- /dev/null +++ b/t/t3900/2-UTF-8.txt @@ -0,0 +1,4 @@ +はれひほふ + +しているのが、いるので。 +濱浜ほれぷりぽれまびぐりろへ。 diff --git a/t/t3900/EUCJP.txt b/t/t3900/EUCJP.txt new file mode 100644 index 0000000000..546f2aac01 --- /dev/null +++ b/t/t3900/EUCJP.txt @@ -0,0 +1,4 @@ +ϤҤۤ + +ƤΤΤǡ +ͤۤפݤޤӤء diff --git a/t/t3900/ISO-2022-JP.txt b/t/t3900/ISO-2022-JP.txt new file mode 100644 index 0000000000..74b533042f --- /dev/null +++ b/t/t3900/ISO-2022-JP.txt @@ -0,0 +1,4 @@ +$B$O$l$R$[$U(B + +$B$7$F$$$k$N$,!"$$$k$N$G!#(B +$B_@IM$[$l$W$j$]$l$^$S$0$j$m$X!#(B diff --git a/t/t3900/ISO-8859-1.txt b/t/t3900/ISO-8859-1.txt new file mode 100644 index 0000000000..7cbef0ee6f --- /dev/null +++ b/t/t3900/ISO-8859-1.txt @@ -0,0 +1,3 @@ + + +bdfg diff --git a/t/t5400-send-pack.sh b/t/t5400-send-pack.sh index 28744b35e1..2c151912a3 100755 --- a/t/t5400-send-pack.sh +++ b/t/t5400-send-pack.sh @@ -8,38 +8,63 @@ test_description='See why rewinding head breaks send-pack ' . ./test-lib.sh -touch cpio-test -test_expect_success 'working cpio' 'echo cpio-test | cpio -o > /dev/null' - -cnt='1' +cnt=64 test_expect_success setup ' + test_tick && + mkdir mozart mozart/is && + echo "Commit #0" >mozart/is/pink && + git-update-index --add mozart/is/pink && tree=$(git-write-tree) && commit=$(echo "Commit #0" | git-commit-tree $tree) && zero=$commit && parent=$zero && - for i in $cnt + i=0 && + while test $i -le $cnt do - sleep 1 && + i=$(($i+1)) && + test_tick && + echo "Commit #$i" >mozart/is/pink && + git-update-index --add mozart/is/pink && + tree=$(git-write-tree) && commit=$(echo "Commit #$i" | git-commit-tree $tree -p $parent) && + git-update-ref refs/tags/commit$i $commit && parent=$commit || return 1 done && git-update-ref HEAD "$commit" && - git-clone -l ./. victim && + git-clone ./. victim && cd victim && git-log && cd .. && git-update-ref HEAD "$zero" && parent=$zero && - for i in $cnt + i=0 && + while test $i -le $cnt do - sleep 1 && + i=$(($i+1)) && + test_tick && + echo "Rebase #$i" >mozart/is/pink && + git-update-index --add mozart/is/pink && + tree=$(git-write-tree) && commit=$(echo "Rebase #$i" | git-commit-tree $tree -p $parent) && + git-update-ref refs/tags/rebase$i $commit && parent=$commit || return 1 done && git-update-ref HEAD "$commit" && echo Rebase && git-log' +test_expect_success 'pack the source repository' ' + git repack -a -d && + git prune +' + +test_expect_success 'pack the destination repository' ' + cd victim && + git repack -a -d && + git prune && + cd .. +' + test_expect_success \ 'pushing rewound head should not barf but require --force' ' # should not fail but refuse to update. diff --git a/t/t5500-fetch-pack.sh b/t/t5500-fetch-pack.sh index f7625a6f46..77c3c575d8 100755 --- a/t/t5500-fetch-pack.sh +++ b/t/t5500-fetch-pack.sh @@ -128,4 +128,54 @@ pull_to_client 2nd "B" $((64*3)) pull_to_client 3rd "A" $((1*3)) # old fails +test_expect_success "clone shallow" "git-clone --depth 2 . shallow" + +(cd shallow; git-count-objects -v) > count.shallow + +test_expect_success "clone shallow object count" \ + "test \"in-pack: 18\" = \"$(grep in-pack count.shallow)\"" + +count_output () { + sed -e '/^in-pack:/d' -e '/^packs:/d' -e '/: 0$/d' "$1" +} + +test_expect_success "clone shallow object count (part 2)" ' + test -z "$(count_output count.shallow)" +' + +test_expect_success "fsck in shallow repo" \ + "(cd shallow; git-fsck-objects --full)" + +#test_done; exit + +add B66 $B65 +add B67 $B66 + +test_expect_success "pull in shallow repo" \ + "(cd shallow; git pull .. B)" + +(cd shallow; git-count-objects -v) > count.shallow +test_expect_success "clone shallow object count" \ + "test \"count: 6\" = \"$(grep count count.shallow)\"" + +add B68 $B67 +add B69 $B68 + +test_expect_success "deepening pull in shallow repo" \ + "(cd shallow; git pull --depth 4 .. B)" + +(cd shallow; git-count-objects -v) > count.shallow +test_expect_success "clone shallow object count" \ + "test \"count: 12\" = \"$(grep count count.shallow)\"" + +test_expect_success "deepening fetch in shallow repo" \ + "(cd shallow; git fetch --depth 4 .. A:A)" + +(cd shallow; git-count-objects -v) > count.shallow +test_expect_success "clone shallow object count" \ + "test \"count: 18\" = \"$(grep count count.shallow)\"" + +test_expect_failure "pull in shallow repo with missing merge base" \ + "(cd shallow; git pull --depth 4 .. A)" + test_done diff --git a/t/t6023-merge-file.sh b/t/t6023-merge-file.sh index 5d9b6f34b8..1c21d8c986 100644 --- a/t/t6023-merge-file.sh +++ b/t/t6023-merge-file.sh @@ -112,5 +112,27 @@ EOF test_expect_success "expected conflict markers, with -L" \ "diff -u test.txt expect.txt" +sed "s/ tu / TU /" < new1.txt > new5.txt +test_expect_failure "conflict in removed tail" \ + "git-merge-file -p orig.txt new1.txt new5.txt > out" + +cat > expect << EOF +Dominus regit me, +et nihil mihi deerit. +In loco pascuae ibi me collocavit, +super aquam refectionis educavit me; +animam meam convertit, +deduxit me super semitas jusitiae, +propter nomen suum. +<<<<<<< orig.txt +======= +Nam et si ambulavero in medio umbrae mortis, +non timebo mala, quoniam TU mecum es: +virga tua et baculus tuus ipsa me consolata sunt. +>>>>>>> new5.txt +EOF + +test_expect_success "expected conflict markers" "diff -u expect out" + test_done diff --git a/t/t6024-recursive-merge.sh b/t/t6024-recursive-merge.sh index 69b18f7d81..31b96257b4 100644 --- a/t/t6024-recursive-merge.sh +++ b/t/t6024-recursive-merge.sh @@ -11,50 +11,54 @@ test_description='Test merge without common ancestors' # X \ # 2 - C - E - G -export GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" -echo 1 > a1 -git add a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 - -git checkout -b A master -echo A > a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 - -git checkout -b B master -echo B > a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 - -git checkout -b D A -git-rev-parse B > .git/MERGE_HEAD -echo D > a1 -git update-index a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D - -git symbolic-ref HEAD refs/heads/other -echo 2 > a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 - -git checkout -b C -echo C > a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 - -git checkout -b E C -git-rev-parse B > .git/MERGE_HEAD -echo E > a1 -git update-index a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E - -git checkout -b G E -git-rev-parse A > .git/MERGE_HEAD -echo G > a1 -git update-index a1 -GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G - -git checkout -b F D -git-rev-parse C > .git/MERGE_HEAD -echo F > a1 -git update-index a1 +GIT_COMMITTER_DATE="2006-12-12 23:28:00 +0100" +export GIT_COMMITTER_DATE + +test_expect_success "setup tests" ' +echo 1 > a1 && +git add a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:00" git commit -m 1 a1 && + +git checkout -b A master && +echo A > a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:01" git commit -m A a1 && + +git checkout -b B master && +echo B > a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:02" git commit -m B a1 && + +git checkout -b D A && +git-rev-parse B > .git/MERGE_HEAD && +echo D > a1 && +git update-index a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:03" git commit -m D && + +git symbolic-ref HEAD refs/heads/other && +echo 2 > a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:04" git commit -m 2 a1 && + +git checkout -b C && +echo C > a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:05" git commit -m C a1 && + +git checkout -b E C && +git-rev-parse B > .git/MERGE_HEAD && +echo E > a1 && +git update-index a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:06" git commit -m E && + +git checkout -b G E && +git-rev-parse A > .git/MERGE_HEAD && +echo G > a1 && +git update-index a1 && +GIT_AUTHOR_DATE="2006-12-12 23:00:07" git commit -m G && + +git checkout -b F D && +git-rev-parse C > .git/MERGE_HEAD && +echo F > a1 && +git update-index a1 && GIT_AUTHOR_DATE="2006-12-12 23:00:08" git commit -m F +' test_expect_failure "combined merge conflicts" "git merge -m final G" diff --git a/t/t9200-git-cvsexportcommit.sh b/t/t9200-git-cvsexportcommit.sh index ca0513b162..315119abff 100755 --- a/t/t9200-git-cvsexportcommit.sh +++ b/t/t9200-git-cvsexportcommit.sh @@ -14,16 +14,18 @@ then exit fi -export CVSROOT=$(pwd)/cvsroot -export CVSWORK=$(pwd)/cvswork +CVSROOT=$(pwd)/cvsroot +CVSWORK=$(pwd)/cvswork +GIT_DIR=$(pwd)/.git +export CVSROOT CVSWORK GIT_DIR + rm -rf "$CVSROOT" "$CVSWORK" mkdir "$CVSROOT" && cvs init && cvs -Q co -d "$CVSWORK" . && -export GIT_DIR=$(pwd)/.git && echo >empty && git add empty && -git commit -a -m "Initial" 2>/dev/null || +git commit -q -a -m "Initial" 2>/dev/null || exit 1 test_expect_success \ diff --git a/t/test-lib.sh b/t/test-lib.sh index f0f9cd6be0..bf108d4226 100755 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -96,6 +96,17 @@ test_count=0 trap 'echo >&5 "FATAL: Unexpected exit with code $?"; exit 1' exit +test_tick () { + if test -z "${test_tick+set}" + then + test_tick=432630000 + else + test_tick=$(($test_tick + 60)) + fi + GIT_COMMITTER_DATE=$test_tick + GIT_AUTHOR_DATE=$test_tick + export GIT_COMMITTER_DATE GIT_AUTHOR_DATE +} # You are not expected to call test_ok_ and test_failure_ directly, use # the text_expect_* functions instead. @@ -125,16 +136,43 @@ test_run_ () { return 0 } +test_skip () { + this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$') + this_test="$this_test.$(expr "$test_count" + 1)" + to_skip= + for skp in $GIT_SKIP_TESTS + do + case "$this_test" in + $skp) + to_skip=t + esac + done + case "$to_skip" in + t) + say >&3 "skipping test: $@" + test_count=$(expr "$test_count" + 1) + say "skip $test_count: $1" + : true + ;; + *) + false + ;; + esac +} + test_expect_failure () { test "$#" = 2 || error "bug in the test script: not 2 parameters to test-expect-failure" - say >&3 "expecting failure: $2" - test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ] + if ! test_skip "$@" then - test_ok_ "$1" - else - test_failure_ "$@" + say >&3 "expecting failure: $2" + test_run_ "$2" + if [ "$?" = 0 -a "$eval_ret" != 0 -a "$eval_ret" -lt 129 ] + then + test_ok_ "$1" + else + test_failure_ "$@" + fi fi echo >&3 "" } @@ -142,13 +180,16 @@ test_expect_failure () { test_expect_success () { test "$#" = 2 || error "bug in the test script: not 2 parameters to test-expect-success" - say >&3 "expecting success: $2" - test_run_ "$2" - if [ "$?" = 0 -a "$eval_ret" = 0 ] + if ! test_skip "$@" then - test_ok_ "$1" - else - test_failure_ "$@" + say >&3 "expecting success: $2" + test_run_ "$2" + if [ "$?" = 0 -a "$eval_ret" = 0 ] + then + test_ok_ "$1" + else + test_failure_ "$@" + fi fi echo >&3 "" } @@ -156,13 +197,16 @@ test_expect_success () { test_expect_code () { test "$#" = 3 || error "bug in the test script: not 3 parameters to test-expect-code" - say >&3 "expecting exit code $1: $3" - test_run_ "$3" - if [ "$?" = 0 -a "$eval_ret" = "$1" ] + if ! test_skip "$@" then - test_ok_ "$2" - else - test_failure_ "$@" + say >&3 "expecting exit code $1: $3" + test_run_ "$3" + if [ "$?" = 0 -a "$eval_ret" = "$1" ] + then + test_ok_ "$2" + else + test_failure_ "$@" + fi fi echo >&3 "" } @@ -176,7 +220,7 @@ test_create_repo () { repo="$1" mkdir "$repo" cd "$repo" || error "Cannot setup test environment" - "$GIT_EXEC_PATH/git" init-db --template=$GIT_EXEC_PATH/templates/blt/ 2>/dev/null || + "$GIT_EXEC_PATH/git" init-db --template=$GIT_EXEC_PATH/templates/blt/ >/dev/null 2>&1 || error "cannot run git init-db -- have you built things yet?" mv .git/hooks .git/hooks-disabled cd "$owd" @@ -223,3 +267,22 @@ test=trash rm -fr "$test" test_create_repo $test cd "$test" + +this_test=$(expr "./$0" : '.*/\(t[0-9]*\)-[^/]*$') +for skp in $GIT_SKIP_TESTS +do + to_skip= + for skp in $GIT_SKIP_TESTS + do + case "$this_test" in + $skp) + to_skip=t + esac + done + case "$to_skip" in + t) + say >&3 "skipping test $this_test altogether" + say "skip all tests in $this_test" + test_done + esac +done diff --git a/templates/hooks--update b/templates/hooks--update index 76d5ac2477..9863a800c8 100644 --- a/templates/hooks--update +++ b/templates/hooks--update @@ -19,7 +19,7 @@ ref_type=$(git cat-file -t "$3") case "$1","$ref_type" in refs/tags/*,commit) echo "*** Un-annotated tags are not allowed in this repo" >&2 - echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." + echo "*** Use 'git tag [ -a | -s ]' for tags you want to propagate." >&2 exit 1;; refs/tags/*,tag) echo "### Pushing version '${1##refs/tags/}' to the masses" >&2 diff --git a/upload-pack.c b/upload-pack.c index 32b06b2e66..c568ef066c 100644 --- a/upload-pack.c +++ b/upload-pack.c @@ -6,6 +6,9 @@ #include "object.h" #include "commit.h" #include "exec_cmd.h" +#include "diff.h" +#include "revision.h" +#include "list-objects.h" static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=nn] <dir>"; @@ -16,6 +19,10 @@ static const char upload_pack_usage[] = "git-upload-pack [--strict] [--timeout=n #define COMMON_KNOWN (1u << 14) #define REACHABLE (1u << 15) +#define SHALLOW (1u << 16) +#define NOT_SHALLOW (1u << 17) +#define CLIENT_SHALLOW (1u << 18) + static unsigned long oldest_have; static int multi_ack, nr_our_refs; @@ -54,6 +61,40 @@ static ssize_t send_client_data(int fd, const char *data, ssize_t sz) return safe_write(fd, data, sz); } +FILE *pack_pipe = NULL; +static void show_commit(struct commit *commit) +{ + if (commit->object.flags & BOUNDARY) + fputc('-', pack_pipe); + if (fputs(sha1_to_hex(commit->object.sha1), pack_pipe) < 0) + die("broken output pipe"); + fputc('\n', pack_pipe); + fflush(pack_pipe); + free(commit->buffer); + commit->buffer = NULL; +} + +static void show_object(struct object_array_entry *p) +{ + /* An object with name "foo\n0000000..." can be used to + * confuse downstream git-pack-objects very badly. + */ + const char *ep = strchr(p->name, '\n'); + if (ep) { + fprintf(pack_pipe, "%s %.*s\n", sha1_to_hex(p->item->sha1), + (int) (ep - p->name), + p->name); + } + else + fprintf(pack_pipe, "%s %s\n", + sha1_to_hex(p->item->sha1), p->name); +} + +static void show_edge(struct commit *commit) +{ + fprintf(pack_pipe, "-%s\n", sha1_to_hex(commit->object.sha1)); +} + static void create_pack_file(void) { /* Pipes between rev-list to pack-objects, pack-objects to us @@ -75,48 +116,40 @@ static void create_pack_file(void) if (!pid_rev_list) { int i; - int args; - const char **argv; - const char **p; - char *buf; + struct rev_info revs; - if (create_full_pack) { - args = 10; - use_thin_pack = 0; /* no point doing it */ - } - else - args = have_obj.nr + want_obj.nr + 5; - p = xmalloc(args * sizeof(char *)); - argv = (const char **) p; - buf = xmalloc(args * 45); + pack_pipe = fdopen(lp_pipe[1], "w"); - dup2(lp_pipe[1], 1); - close(0); - close(lp_pipe[0]); - close(lp_pipe[1]); - *p++ = "rev-list"; - *p++ = use_thin_pack ? "--objects-edge" : "--objects"; if (create_full_pack) - *p++ = "--all"; - else { + use_thin_pack = 0; /* no point doing it */ + init_revisions(&revs, NULL); + revs.tag_objects = 1; + revs.tree_objects = 1; + revs.blob_objects = 1; + if (use_thin_pack) + revs.edge_hint = 1; + + if (create_full_pack) { + const char *args[] = {"rev-list", "--all", NULL}; + setup_revisions(2, args, &revs, NULL); + } else { for (i = 0; i < want_obj.nr; i++) { struct object *o = want_obj.objects[i].item; - *p++ = buf; - memcpy(buf, sha1_to_hex(o->sha1), 41); - buf += 41; + /* why??? */ + o->flags &= ~UNINTERESTING; + add_pending_object(&revs, o, NULL); } - } - if (!create_full_pack) for (i = 0; i < have_obj.nr; i++) { struct object *o = have_obj.objects[i].item; - *p++ = buf; - *buf++ = '^'; - memcpy(buf, sha1_to_hex(o->sha1), 41); - buf += 41; + o->flags |= UNINTERESTING; + add_pending_object(&revs, o, NULL); } - *p++ = NULL; - execv_git_cmd(argv); - die("git-upload-pack: unable to exec git-rev-list"); + setup_revisions(0, NULL, &revs, NULL); + } + prepare_revision_walk(&revs); + mark_edges_uninteresting(revs.commits, &revs, show_edge); + traverse_commit_list(&revs, show_commit, show_object); + exit(0); } if (pipe(pu_pipe) < 0) @@ -456,8 +489,9 @@ static int get_common_commits(void) static void receive_needs(void) { + struct object_array shallows = {0, 0, NULL}; static char line[1000]; - int len; + int len, depth = 0; for (;;) { struct object *o; @@ -465,8 +499,29 @@ static void receive_needs(void) len = packet_read_line(0, line, sizeof(line)); reset_timeout(); if (!len) - return; + break; + if (!strncmp("shallow ", line, 8)) { + unsigned char sha1[20]; + struct object *object; + use_thin_pack = 0; + if (get_sha1(line + 8, sha1)) + die("invalid shallow line: %s", line); + object = parse_object(sha1); + if (!object) + die("did not find object for %s", line); + object->flags |= CLIENT_SHALLOW; + add_object_array(object, NULL, &shallows); + continue; + } + if (!strncmp("deepen ", line, 7)) { + char *end; + use_thin_pack = 0; + depth = strtol(line + 7, &end, 0); + if (end == line + 7 || depth <= 0) + die("Invalid deepen: %s", line); + continue; + } if (strncmp("want ", line, 5) || get_sha1_hex(line+5, sha1_buf)) die("git-upload-pack: protocol error, " @@ -498,11 +553,58 @@ static void receive_needs(void) add_object_array(o, NULL, &want_obj); } } + if (depth == 0 && shallows.nr == 0) + return; + if (depth > 0) { + struct commit_list *result, *backup; + int i; + backup = result = get_shallow_commits(&want_obj, depth, + SHALLOW, NOT_SHALLOW); + while (result) { + struct object *object = &result->item->object; + if (!(object->flags & (CLIENT_SHALLOW|NOT_SHALLOW))) { + packet_write(1, "shallow %s", + sha1_to_hex(object->sha1)); + register_shallow(object->sha1); + } + result = result->next; + } + free_commit_list(backup); + for (i = 0; i < shallows.nr; i++) { + struct object *object = shallows.objects[i].item; + if (object->flags & NOT_SHALLOW) { + struct commit_list *parents; + packet_write(1, "unshallow %s", + sha1_to_hex(object->sha1)); + object->flags &= ~CLIENT_SHALLOW; + /* make sure the real parents are parsed */ + unregister_shallow(object->sha1); + object->parsed = 0; + parse_commit((struct commit *)object); + parents = ((struct commit *)object)->parents; + while (parents) { + add_object_array(&parents->item->object, + NULL, &want_obj); + parents = parents->next; + } + } + /* make sure commit traversal conforms to client */ + register_shallow(object->sha1); + } + packet_flush(1); + } else + if (shallows.nr > 0) { + int i; + for (i = 0; i < shallows.nr; i++) + register_shallow(shallows.objects[i].item->sha1); + } + free(shallows.objects); } static int send_ref(const char *refname, const unsigned char *sha1, int flag, void *cb_data) { - static const char *capabilities = "multi_ack thin-pack side-band side-band-64k ofs-delta"; + static const char *capabilities = "multi_ack thin-pack side-band" + " side-band-64k ofs-delta shallow"; struct object *o = parse_object(sha1); if (!o) @@ -276,3 +276,57 @@ void print_wrapped_text(const char *text, int indent, int indent2, int width) } } } + +/* + * Given a buffer and its encoding, return it re-encoded + * with iconv. If the conversion fails, returns NULL. + */ +#ifndef NO_ICONV +char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding) +{ + iconv_t conv; + size_t insz, outsz, outalloc; + char *out, *outpos, *cp; + + if (!in_encoding) + return NULL; + conv = iconv_open(out_encoding, in_encoding); + if (conv == (iconv_t) -1) + return NULL; + insz = strlen(in); + outsz = insz; + outalloc = outsz + 1; /* for terminating NUL */ + out = xmalloc(outalloc); + outpos = out; + cp = (char *)in; + + while (1) { + size_t cnt = iconv(conv, &cp, &insz, &outpos, &outsz); + + if (cnt == -1) { + size_t sofar; + if (errno != E2BIG) { + free(out); + iconv_close(conv); + return NULL; + } + /* insz has remaining number of bytes. + * since we started outsz the same as insz, + * it is likely that insz is not enough for + * converting the rest. + */ + sofar = outpos - out; + outalloc = sofar + insz * 2 + 32; + out = xrealloc(out, outalloc); + outpos = out + sofar; + outsz = outalloc - sofar - 1; + } + else { + *outpos = '\0'; + break; + } + } + iconv_close(conv); + return out; +} +#endif @@ -5,4 +5,10 @@ int utf8_width(const char **start); int is_utf8(const char *text); void print_wrapped_text(const char *text, int indent, int indent2, int len); +#ifndef NO_ICONV +char *reencode_string(const char *in, const char *out_encoding, const char *in_encoding); +#else +#define reencode_string(a,b,c) NULL +#endif + #endif diff --git a/xdiff/xmerge.c b/xdiff/xmerge.c index 352207e516..294450b899 100644 --- a/xdiff/xmerge.c +++ b/xdiff/xmerge.c @@ -190,6 +190,10 @@ static int xdl_refine_conflicts(xdfenv_t *xe1, xdfenv_t *xe2, xdmerge_t *m, if (m->mode) continue; + /* no sense refining a conflict when one side is empty */ + if (m->chg1 == 0 || m->chg2 == 0) + continue; + /* * This probably does not work outside git, since * we have a very simple mmfile structure. |