diff options
54 files changed, 2905 insertions, 2456 deletions
diff --git a/Documentation/git-am.txt b/Documentation/git-am.txt index 02cabc935e..910457d3b3 100644 --- a/Documentation/git-am.txt +++ b/Documentation/git-am.txt @@ -9,7 +9,8 @@ git-am - Apply a series of patches in a mailbox SYNOPSIS -------- [verse] -'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox>... +'git-am' [--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] + [--interactive] [--whitespace=<option>] <mbox>... 'git-am' [--skip | --resolved] DESCRIPTION @@ -46,6 +47,10 @@ OPTIONS Skip the current patch. This is only meaningful when restarting an aborted patch. +--whitespace=<option>:: + This flag is passed to the `git-apply` program that applies + the patch. + --interactive:: Run interactively, just like git-applymbox. @@ -80,7 +85,7 @@ names. SEE ALSO -------- -gitlink:git-applymbox[1], gitlink:git-applypatch[1]. +gitlink:git-applymbox[1], gitlink:git-applypatch[1], gitlink:git-apply[1]. Author diff --git a/Documentation/git-apply.txt b/Documentation/git-apply.txt index 75076b6121..1c64a1aa82 100644 --- a/Documentation/git-apply.txt +++ b/Documentation/git-apply.txt @@ -11,6 +11,7 @@ SYNOPSIS [verse] 'git-apply' [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] + [--whitespace=<nowarn|warn|error|error-all|strip>] [<patch>...] DESCRIPTION @@ -97,6 +98,35 @@ OPTIONS result. This allows binary files to be patched in a very limited way. +--whitespace=<option>:: + When applying a patch, detect a new or modified line + that ends with trailing whitespaces (this includes a + line that solely consists of whitespaces). By default, + the command outputs warning messages and applies the + patch. + When `git-apply` is used for statistics and not applying a + patch, it defaults to `nowarn`. + You can use different `<option>` to control this + behaviour: ++ +* `nowarn` turns off the trailing whitespace warning. +* `warn` outputs warnings for a few such errors, but applies the + patch (default). +* `error` outputs warnings for a few such errors, and refuses + to apply the patch. +* `error-all` is similar to `error` but shows all errors. +* `strip` outputs warnings for a few such errors, strips out the + trailing whitespaces and applies the patch. + + +Configuration +------------- + +apply.whitespace:: + When no `--whitespace` flag is given from the command + line, this configuration item is used as the default. + + Author ------ Written by Linus Torvalds <torvalds@osdl.org> diff --git a/Documentation/git-cvsserver.txt b/Documentation/git-cvsserver.txt index 88f07ff15d..19c9c51cff 100644 --- a/Documentation/git-cvsserver.txt +++ b/Documentation/git-cvsserver.txt @@ -54,6 +54,30 @@ INSTALLATION of branches in git). $ cvs co -d mylocaldir master +Eclipse CVS Client Notes +------------------------ + +To get a checkout with the Eclipse CVS client: + +1. Create a new project from CVS checkout, giving it repository and module +2. Context Menu->Team->Share Project... +3. Enter the repository and module information again and click Finish +4. The Synchronize view appears. Untick "launch commit wizard" to avoid +committing the .project file, and select HEAD as the tag to synchronize to. +Update all incoming changes. + +Note that most versions of Eclipse ignore CVS_SERVER (which you can set in +the Preferences->Team->CVS->ExtConnection pane), so you may have to +rename, alias or symlink git-cvsserver to 'cvs' on the server. + +Clients known to work +--------------------- + +CVS 1.12.9 on Debian +CVS 1.11.17 on MacOSX (from Fink package) +Eclipse 3.0, 3.1.2 on MacOSX (see Eclipse CVS Client Notes) +TortoiseCVS + Operations supported -------------------- diff --git a/Documentation/git-read-tree.txt b/Documentation/git-read-tree.txt index 6fbd6d9368..844cfda8d2 100644 --- a/Documentation/git-read-tree.txt +++ b/Documentation/git-read-tree.txt @@ -8,7 +8,7 @@ git-read-tree - Reads tree information into the index SYNOPSIS -------- -'git-read-tree' (<tree-ish> | [[-m | --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) +'git-read-tree' (<tree-ish> | [[-m [--aggressive]| --reset] [-u | -i]] <tree-ish1> [<tree-ish2> [<tree-ish3>]]) DESCRIPTION @@ -50,6 +50,19 @@ OPTIONS trees that are not directly related to the current working tree status into a temporary index file. +--aggressive:: + Usually a three-way merge by `git-read-tree` resolves + the merge for really trivial cases and leaves other + cases unresolved in the index, so that Porcelains can + implement different merge policies. This flag makes the + command to resolve a few more cases internally: ++ +* when one side removes a path and the other side leaves the path + unmodified. The resolution is to remove that path. +* when both sides remove a path. The resolution is to remove that path. +* when both sides adds a path identically. The resolution + is to add that path. + <tree-ish#>:: The id of the tree object(s) to be read/merged. diff --git a/Documentation/git-repo-config.txt b/Documentation/git-repo-config.txt index 33fcde452a..00efde5f0f 100644 --- a/Documentation/git-repo-config.txt +++ b/Documentation/git-repo-config.txt @@ -8,6 +8,7 @@ git-repo-config - Get and set options in .git/config. SYNOPSIS -------- +[verse] 'git-repo-config' [type] name [value [value_regex]] 'git-repo-config' [type] --replace-all name [value [value_regex]] 'git-repo-config' [type] --get name [value_regex] diff --git a/Documentation/git-rev-list.txt b/Documentation/git-rev-list.txt index 1c6146c764..8255ae1bce 100644 --- a/Documentation/git-rev-list.txt +++ b/Documentation/git-rev-list.txt @@ -16,9 +16,9 @@ SYNOPSIS [ \--no-merges ] [ \--remove-empty ] [ \--all ] - [ [ \--merge-order [ \--show-breaks ] ] | [ \--topo-order ] ] + [ \--topo-order ] [ \--parents ] - [ \--objects [ \--unpacked ] ] + [ [\--objects | \--objects-edge] [ \--unpacked ] ] [ \--pretty | \--header ] [ \--bisect ] <commit>... [ \-- <paths>... ] @@ -53,6 +53,14 @@ OPTIONS which I need to download if I have the commit object 'bar', but not 'foo'". +--objects-edge:: + Similar to `--objects`, but also print the IDs of + excluded commits refixed with a `-` character. This is + used by `git-pack-objects` to build 'thin' pack, which + records objects in deltified form based on objects + contained in these excluded commits to reduce network + traffic. + --unpacked:: Only useful with `--objects`; print the object IDs that are not in packs. @@ -94,57 +102,10 @@ OPTIONS topological order (i.e. descendant commits are shown before their parents). ---merge-order:: - When specified the commit history is decomposed into a unique - sequence of minimal, non-linear epochs and maximal, linear epochs. - Non-linear epochs are then linearised by sorting them into merge - order, which is described below. -+ -Maximal, linear epochs correspond to periods of sequential development. -Minimal, non-linear epochs correspond to periods of divergent development -followed by a converging merge. The theory of epochs is described in more -detail at -link:http://blackcubes.dyndns.org/epoch/[http://blackcubes.dyndns.org/epoch/]. -+ -The merge order for a non-linear epoch is defined as a linearisation for which -the following invariants are true: -+ - 1. if a commit P is reachable from commit N, commit P sorts after commit N - in the linearised list. - 2. if Pi and Pj are any two parents of a merge M (with i < j), then any - commit N, such that N is reachable from Pj but not reachable from Pi, - sorts before all commits reachable from Pi. -+ -Invariant 1 states that later commits appear before earlier commits they are -derived from. -+ -Invariant 2 states that commits unique to "later" parents in a merge, appear -before all commits from "earlier" parents of a merge. - ---show-breaks:: - Each item of the list is output with a 2-character prefix consisting - of one of: (|), (^), (=) followed by a space. -+ -Commits marked with (=) represent the boundaries of minimal, non-linear epochs -and correspond either to the start of a period of divergent development or to -the end of such a period. -+ -Commits marked with (|) are direct parents of commits immediately preceding -the marked commit in the list. -+ -Commits marked with (^) are not parents of the immediately preceding commit. -These "breaks" represent necessary discontinuities implied by trying to -represent an arbitrary DAG in a linear form. -+ -`--show-breaks` is only valid if `--merge-order` is also specified. - - Author ------ Written by Linus Torvalds <torvalds@osdl.org> -Original *--merge-order* logic by Jon Seymour <jon.seymour@gmail.com> - Documentation -------------- Documentation by David Greaves, Junio C Hamano and the git-list <git@vger.kernel.org>. diff --git a/Documentation/git-svnimport.txt b/Documentation/git-svnimport.txt index 912a80865e..9d3865719c 100644 --- a/Documentation/git-svnimport.txt +++ b/Documentation/git-svnimport.txt @@ -9,6 +9,7 @@ git-svnimport - Import a SVN repository into git SYNOPSIS -------- +[verse] 'git-svnimport' [ -o <branch-for-HEAD> ] [ -h ] [ -v ] [ -d | -D ] [ -C <GIT_repository> ] [ -i ] [ -u ] [-l limit_rev] [ -b branch_subdir ] [ -T trunk_subdir ] [ -t tag_subdir ] @@ -82,6 +83,11 @@ When importing incrementally, you might need to edit the .git/svn2git file. "username". If encountering a commit made by a user not in the list, abort. + For convenience, this data is saved to $GIT_DIR/svn-authors + each time the -A option is provided, and read from that same + file each time git-svnimport is run with an existing GIT + repository without -A. + -m:: Attempt to detect merges based on the commit message. This option will enable default regexes that try to capture the name source diff --git a/Documentation/git-tools.txt b/Documentation/git-tools.txt new file mode 100644 index 0000000000..00e57a69ae --- /dev/null +++ b/Documentation/git-tools.txt @@ -0,0 +1,97 @@ +A short git tools survey +======================== + + +Introduction +------------ + +Apart from git contrib/ area there are some others third-party tools +you may want to look. + +This document presents a brief summary of each tool and the corresponding +link. + + +Alternative/Augmentative Procelains +----------------------------------- + + - *Cogito* (http://www.kernel.org/pub/software/scm/cogito/) + + Cogito is a version control system layered on top of the git tree history + storage system. It aims at seamless user interface and ease of use, + providing generally smoother user experience than the "raw" Core GIT + itself and indeed many other version control systems. + + + - *pg* (http://www.spearce.org/category/projects/scm/pg/) + + pg is a shell script wrapper around GIT to help the user manage a set of + patches to files. pg is somewhat like quilt or StGIT, but it does have a + slightly different feature set. + + + - *StGit* (http://www.procode.org/stgit/) + + Stacked GIT provides a quilt-like patch management functionality in the + GIT environment. You can easily manage your patches in the scope of GIT + until they get merged upstream. + + +History Viewers +--------------- + + - *gitk* (shipped with git-core) + + gitk is a simple TK GUI for browsing history of GIT repositories easily. + + + - *gitview* (contrib/) + + gitview is a GTK based repository browser for git + + + - *gitweb* (ftp://ftp.kernel.org/pub/software/scm/gitweb/) + + GITweb provides full-fledged web interface for GIT repositories. + + + - *qgit* (http://digilander.libero.it/mcostalba/) + + QGit is a git/StGIT GUI viewer built on Qt/C++. QGit could be used + to browse history and directory tree, view annotated files, commit + changes cherry picking single files or applying patches. + Currently it is the fastest and most feature rich among the git + viewers and commit tools. + + + +Foreign SCM interface +--------------------- + + - *git-svn* (contrib/) + + git-svn is a simple conduit for changesets between a single Subversion + branch and git. + + + - *quilt2git / git2quilt* (http://home-tj.org/wiki/index.php/Misc) + + These utilities convert patch series in a quilt repository and commit + series in git back and forth. + + +Others +------ + + - *(h)gct* (http://www.cyd.liu.se/users/~freku045/gct/) + + Commit Tool or (h)gct is a GUI enabled commit tool for git and + Mercurial (hg). It allows the user to view diffs, select which files + to committed (or ignored / reverted) write commit messages and + perform the commit itself. + + - *git.el* (contrib/) + + This is an Emacs interface for git. The user interface is modeled on + pcl-cvs. It has been developed on Emacs 21 and will probably need some + tweaking to work on XEmacs. diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 1056b7c817..d6d1ae0338 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -7,8 +7,11 @@ DEF_VER=v1.2.GIT # (included in release tarballs), then default if VN=$(git-describe --abbrev=4 HEAD 2>/dev/null); then VN=$(echo "$VN" | sed -e 's/-/./g'); -else +elif test -f version +then VN=$(cat version) || VN="$DEF_VER" +else + VN="$DEF_VER" fi VN=$(expr "$VN" : v*'\(.*\)') @@ -40,9 +40,7 @@ Issues of note: If you don't have openssl, you can use one of the SHA1 libraries that come with git (git includes the one from Mozilla, and has - its own PowerPC-optimized one too - see the Makefile), and you - can avoid the bignum support by excising git-rev-list support - for "--merge-order" (by hand). + its own PowerPC and ARM optimized ones too - see the Makefile). - "libcurl" and "curl" executable. git-http-fetch and git-fetch use them. If you do not use http @@ -6,8 +6,8 @@ all: # on non-x86 architectures (e.g. PowerPC), while the OpenSSL version (default # choice) has very fast version optimized for i586. # -# Define NO_OPENSSL environment variable if you do not have OpenSSL. You will -# miss out git-rev-list --merge-order. This also implies MOZILLA_SHA1. +# Define NO_OPENSSL environment variable if you do not have OpenSSL. +# This also implies MOZILLA_SHA1. # # Define NO_CURL if you do not have curl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// @@ -165,7 +165,7 @@ PROGRAMS = \ git-upload-pack$X git-verify-pack$X git-write-tree$X \ git-update-ref$X git-symbolic-ref$X git-check-ref-format$X \ git-name-rev$X git-pack-redundant$X git-repo-config$X git-var$X \ - git-describe$X git-merge-tree$X + git-describe$X git-merge-tree$X git-blame$X # what 'all' will build and 'install' will install, in gitexecdir ALL_PROGRAMS = $(PROGRAMS) $(SIMPLE_PROGRAMS) $(SCRIPTS) @@ -190,9 +190,9 @@ PYMODULES = \ LIB_FILE=libgit.a LIB_H = \ - blob.h cache.h commit.h count-delta.h csum-file.h delta.h \ - diff.h epoch.h object.h pack.h pkt-line.h quote.h refs.h \ - run-command.h strbuf.h tag.h tree.h git-compat-util.h + blob.h cache.h commit.h csum-file.h delta.h \ + diff.h object.h pack.h pkt-line.h quote.h refs.h \ + run-command.h strbuf.h tag.h tree.h git-compat-util.h revision.h DIFF_OBJS = \ diff.o diffcore-break.o diffcore-order.o diffcore-pathspec.o \ @@ -200,13 +200,13 @@ DIFF_OBJS = \ diffcore-delta.o LIB_OBJS = \ - blob.o commit.o connect.o count-delta.o csum-file.o \ + blob.o commit.o connect.o csum-file.o \ date.o diff-delta.o entry.o exec_cmd.o ident.o index.o \ object.o pack-check.o patch-delta.o path.o pkt-line.o \ quote.o read-cache.o refs.o run-command.o \ server-info.o setup.o sha1_file.o sha1_name.o strbuf.o \ tag.o tree.o usage.o config.o environment.o ctype.o copy.o \ - fetch-clone.o \ + fetch-clone.o revision.o pager.o \ $(DIFF_OBJS) LIBS = $(LIB_FILE) @@ -329,7 +329,6 @@ ifndef NO_CURL endif ifndef NO_OPENSSL - LIB_OBJS += epoch.o OPENSSL_LIBSSL = -lssl ifdef OPENSSLDIR # Again this may be problematic -- gcc does not always want -R. @@ -455,7 +454,7 @@ strip: $(PROGRAMS) git$X git$X: git.c $(LIB_FILE) $(CC) -DGIT_VERSION='"$(GIT_VERSION)"' \ - $(CFLAGS) $(COMPAT_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) + $(ALL_CFLAGS) -o $@ $(filter %.c,$^) $(LIB_FILE) $(LIBS) $(patsubst %.sh,%,$(SCRIPT_SH)) : % : %.sh rm -f $@ @@ -32,7 +32,57 @@ static int no_add = 0; static int show_index_info = 0; static int line_termination = '\n'; static const char apply_usage[] = -"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] <patch>..."; +"git-apply [--stat] [--numstat] [--summary] [--check] [--index] [--apply] [--no-add] [--index-info] [--allow-binary-replacement] [-z] [-pNUM] [--whitespace=<nowarn|warn|error|error-all|strip>] <patch>..."; + +static enum whitespace_eol { + nowarn_whitespace, + warn_on_whitespace, + error_on_whitespace, + strip_whitespace, +} new_whitespace = warn_on_whitespace; +static int whitespace_error = 0; +static int squelch_whitespace_errors = 5; +static int applied_after_stripping = 0; +static const char *patch_input_file = NULL; + +static void parse_whitespace_option(const char *option) +{ + if (!option) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "warn")) { + new_whitespace = warn_on_whitespace; + return; + } + if (!strcmp(option, "nowarn")) { + new_whitespace = nowarn_whitespace; + return; + } + if (!strcmp(option, "error")) { + new_whitespace = error_on_whitespace; + return; + } + if (!strcmp(option, "error-all")) { + new_whitespace = error_on_whitespace; + squelch_whitespace_errors = 0; + return; + } + if (!strcmp(option, "strip")) { + new_whitespace = strip_whitespace; + return; + } + die("unrecognized whitespace option '%s'", option); +} + +static void set_default_whitespace_mode(const char *whitespace_option) +{ + if (!whitespace_option && !apply_default_whitespace) { + new_whitespace = (apply + ? warn_on_whitespace + : nowarn_whitespace); + } +} /* * For "diff-stat" like behaviour, we keep track of the biggest change @@ -815,6 +865,25 @@ static int parse_fragment(char *line, unsigned long size, struct patch *patch, s oldlines--; break; case '+': + /* + * We know len is at least two, since we have a '+' and + * we checked that the last character was a '\n' above. + * That is, an addition of an empty line would check + * the '+' here. Sneaky... + */ + if ((new_whitespace != nowarn_whitespace) && + isspace(line[len-2])) { + whitespace_error++; + if (squelch_whitespace_errors && + squelch_whitespace_errors < + whitespace_error) + ; + else { + fprintf(stderr, "Adds trailing whitespace.\n%s:%d:%.*s\n", + patch_input_file, + linenr, len-2, line+1); + } + } added++; newlines--; break; @@ -1092,6 +1161,28 @@ struct buffer_desc { unsigned long alloc; }; +static int apply_line(char *output, const char *patch, int plen) +{ + /* plen is number of bytes to be copied from patch, + * starting at patch+1 (patch[0] is '+'). Typically + * patch[plen] is '\n'. + */ + int add_nl_to_tail = 0; + if ((new_whitespace == strip_whitespace) && + 1 < plen && isspace(patch[plen-1])) { + if (patch[plen] == '\n') + add_nl_to_tail = 1; + plen--; + while (0 < plen && isspace(patch[plen])) + plen--; + applied_after_stripping++; + } + memcpy(output, patch + 1, plen); + if (add_nl_to_tail) + output[plen++] = '\n'; + return plen; +} + static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) { char *buf = desc->buffer; @@ -1127,10 +1218,9 @@ static int apply_one_fragment(struct buffer_desc *desc, struct fragment *frag) break; /* Fall-through for ' ' */ case '+': - if (*patch != '+' || !no_add) { - memcpy(new + newsize, patch + 1, plen); - newsize += plen; - } + if (*patch != '+' || !no_add) + newsize += apply_line(new + newsize, patch, + plen); break; case '@': case '\\': /* Ignore it, we already handled it */ @@ -1699,7 +1789,7 @@ static int use_patch(struct patch *p) return 1; } -static int apply_patch(int fd) +static int apply_patch(int fd, const char *filename) { int newfd; unsigned long offset, size; @@ -1707,6 +1797,7 @@ static int apply_patch(int fd) struct patch *list = NULL, **listp = &list; int skipped_patch = 0; + patch_input_file = filename; if (!buffer) return -1; offset = 0; @@ -1733,6 +1824,9 @@ static int apply_patch(int fd) } newfd = -1; + if (whitespace_error && (new_whitespace == error_on_whitespace)) + apply = 0; + write_index = check_index && apply; if (write_index) newfd = hold_index_file_for_update(&cache_file, get_index_file()); @@ -1769,17 +1863,28 @@ static int apply_patch(int fd) return 0; } +static int git_apply_config(const char *var, const char *value) +{ + if (!strcmp(var, "apply.whitespace")) { + apply_default_whitespace = strdup(value); + return 0; + } + return git_default_config(var, value); +} + + int main(int argc, char **argv) { int i; int read_stdin = 1; + const char *whitespace_option = NULL; for (i = 1; i < argc; i++) { const char *arg = argv[i]; int fd; if (!strcmp(arg, "-")) { - apply_patch(0); + apply_patch(0, "<stdin>"); read_stdin = 0; continue; } @@ -1839,11 +1944,18 @@ int main(int argc, char **argv) line_termination = 0; continue; } + if (!strncmp(arg, "--whitespace=", 13)) { + whitespace_option = arg + 13; + parse_whitespace_option(arg + 13); + continue; + } if (check_index && prefix_length < 0) { prefix = setup_git_directory(); prefix_length = prefix ? strlen(prefix) : 0; - git_config(git_default_config); + git_config(git_apply_config); + if (!whitespace_option && apply_default_whitespace) + parse_whitespace_option(apply_default_whitespace); } if (0 < prefix_length) arg = prefix_filename(prefix, prefix_length, arg); @@ -1852,10 +1964,38 @@ int main(int argc, char **argv) if (fd < 0) usage(apply_usage); read_stdin = 0; - apply_patch(fd); + set_default_whitespace_mode(whitespace_option); + apply_patch(fd, arg); close(fd); } + set_default_whitespace_mode(whitespace_option); if (read_stdin) - apply_patch(0); + apply_patch(0, "<stdin>"); + if (whitespace_error) { + if (squelch_whitespace_errors && + squelch_whitespace_errors < whitespace_error) { + int squelched = + whitespace_error - squelch_whitespace_errors; + fprintf(stderr, "warning: squelched %d whitespace error%s\n", + squelched, + squelched == 1 ? "" : "s"); + } + if (new_whitespace == error_on_whitespace) + die("%d line%s add%s trailing whitespaces.", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + if (applied_after_stripping) + fprintf(stderr, "warning: %d line%s applied after" + " stripping trailing whitespaces.\n", + applied_after_stripping, + applied_after_stripping == 1 ? "" : "s"); + else if (whitespace_error) + fprintf(stderr, "warning: %d line%s add%s trailing" + " whitespaces.\n", + whitespace_error, + whitespace_error == 1 ? "" : "s", + whitespace_error == 1 ? "s" : ""); + } return 0; } diff --git a/blame.c b/blame.c new file mode 100644 index 0000000000..7308c36d23 --- /dev/null +++ b/blame.c @@ -0,0 +1,562 @@ +/* + * Copyright (C) 2006, Fredrik Kuivinen <freku045@student.liu.se> + */ + +#include <assert.h> +#include <time.h> +#include <sys/time.h> + +#include "cache.h" +#include "refs.h" +#include "tag.h" +#include "commit.h" +#include "tree.h" +#include "blob.h" +#include "diff.h" +#include "revision.h" + +#define DEBUG 0 + +struct commit **blame_lines; +int num_blame_lines; + +struct util_info { + int *line_map; + unsigned char sha1[20]; /* blob sha, not commit! */ + char *buf; + unsigned long size; + int num_lines; +// const char* path; +}; + +struct chunk { + int off1, len1; // --- + int off2, len2; // +++ +}; + +struct patch { + struct chunk *chunks; + int num; +}; + +static void get_blob(struct commit *commit); + +/* Only used for statistics */ +static int num_get_patch = 0; +static int num_commits = 0; +static int patch_time = 0; + +#define TEMPFILE_PATH_LEN 60 +static struct patch *get_patch(struct commit *commit, struct commit *other) +{ + struct patch *ret; + struct util_info *info_c = (struct util_info *)commit->object.util; + struct util_info *info_o = (struct util_info *)other->object.util; + char tmp_path1[TEMPFILE_PATH_LEN], tmp_path2[TEMPFILE_PATH_LEN]; + char diff_cmd[TEMPFILE_PATH_LEN*2 + 20]; + struct timeval tv_start, tv_end; + int fd; + FILE *fin; + char buf[1024]; + + ret = xmalloc(sizeof(struct patch)); + ret->chunks = NULL; + ret->num = 0; + + get_blob(commit); + get_blob(other); + + gettimeofday(&tv_start, NULL); + + fd = git_mkstemp(tmp_path1, TEMPFILE_PATH_LEN, "git-blame-XXXXXX"); + if (fd < 0) + die("unable to create temp-file: %s", strerror(errno)); + + if (xwrite(fd, info_c->buf, info_c->size) != info_c->size) + die("write failed: %s", strerror(errno)); + close(fd); + + fd = git_mkstemp(tmp_path2, TEMPFILE_PATH_LEN, "git-blame-XXXXXX"); + if (fd < 0) + die("unable to create temp-file: %s", strerror(errno)); + + if (xwrite(fd, info_o->buf, info_o->size) != info_o->size) + die("write failed: %s", strerror(errno)); + close(fd); + + sprintf(diff_cmd, "diff -u0 %s %s", tmp_path1, tmp_path2); + fin = popen(diff_cmd, "r"); + if (!fin) + die("popen failed: %s", strerror(errno)); + + while (fgets(buf, sizeof(buf), fin)) { + struct chunk *chunk; + char *start, *sp; + + if (buf[0] != '@' || buf[1] != '@') + continue; + + if (DEBUG) + printf("chunk line: %s", buf); + ret->num++; + ret->chunks = xrealloc(ret->chunks, + sizeof(struct chunk) * ret->num); + chunk = &ret->chunks[ret->num - 1]; + + assert(!strncmp(buf, "@@ -", 4)); + + start = buf + 4; + sp = index(start, ' '); + *sp = '\0'; + if (index(start, ',')) { + int ret = + sscanf(start, "%d,%d", &chunk->off1, &chunk->len1); + assert(ret == 2); + } else { + int ret = sscanf(start, "%d", &chunk->off1); + assert(ret == 1); + chunk->len1 = 1; + } + *sp = ' '; + + start = sp + 1; + sp = index(start, ' '); + *sp = '\0'; + if (index(start, ',')) { + int ret = + sscanf(start, "%d,%d", &chunk->off2, &chunk->len2); + assert(ret == 2); + } else { + int ret = sscanf(start, "%d", &chunk->off2); + assert(ret == 1); + chunk->len2 = 1; + } + *sp = ' '; + + if (chunk->len1 == 0) + chunk->off1++; + if (chunk->len2 == 0) + chunk->off2++; + + if (chunk->off1 > 0) + chunk->off1--; + if (chunk->off2 > 0) + chunk->off2--; + + assert(chunk->off1 >= 0); + assert(chunk->off2 >= 0); + } + pclose(fin); + unlink(tmp_path1); + unlink(tmp_path2); + + gettimeofday(&tv_end, NULL); + patch_time += 1000000 * (tv_end.tv_sec - tv_start.tv_sec) + + tv_end.tv_usec - tv_start.tv_usec; + + num_get_patch++; + return ret; +} + +static void free_patch(struct patch *p) +{ + free(p->chunks); + free(p); +} + +static int get_blob_sha1_internal(unsigned char *sha1, const char *base, + int baselen, const char *pathname, + unsigned mode, int stage); + +static unsigned char blob_sha1[20]; +static int get_blob_sha1(struct tree *t, const char *pathname, + unsigned char *sha1) +{ + int i; + const char *pathspec[2]; + pathspec[0] = pathname; + pathspec[1] = NULL; + memset(blob_sha1, 0, sizeof(blob_sha1)); + read_tree_recursive(t, "", 0, 0, pathspec, get_blob_sha1_internal); + + for (i = 0; i < 20; i++) { + if (blob_sha1[i] != 0) + break; + } + + if (i == 20) + return -1; + + memcpy(sha1, blob_sha1, 20); + return 0; +} + +static int get_blob_sha1_internal(unsigned char *sha1, const char *base, + int baselen, const char *pathname, + unsigned mode, int stage) +{ + if (S_ISDIR(mode)) + return READ_TREE_RECURSIVE; + + memcpy(blob_sha1, sha1, 20); + return -1; +} + +static void get_blob(struct commit *commit) +{ + struct util_info *info = commit->object.util; + char type[20]; + + if (info->buf) + return; + + info->buf = read_sha1_file(info->sha1, type, &info->size); + + assert(!strcmp(type, "blob")); +} + +/* For debugging only */ +static void print_patch(struct patch *p) +{ + int i; + printf("Num chunks: %d\n", p->num); + for (i = 0; i < p->num; i++) { + printf("%d,%d %d,%d\n", p->chunks[i].off1, p->chunks[i].len1, + p->chunks[i].off2, p->chunks[i].len2); + } +} + +/* For debugging only */ +static void print_map(struct commit *cmit, struct commit *other) +{ + struct util_info *util = cmit->object.util; + struct util_info *util2 = other->object.util; + + int i; + int max = + util->num_lines > + util2->num_lines ? util->num_lines : util2->num_lines; + int num; + + for (i = 0; i < max; i++) { + printf("i: %d ", i); + num = -1; + + if (i < util->num_lines) { + num = util->line_map[i]; + printf("%d\t", num); + } else + printf("\t"); + + if (i < util2->num_lines) { + int num2 = util2->line_map[i]; + printf("%d\t", num2); + if (num != -1 && num2 != num) + printf("---"); + } else + printf("\t"); + + printf("\n"); + } +} + +// p is a patch from commit to other. +static void fill_line_map(struct commit *commit, struct commit *other, + struct patch *p) +{ + struct util_info *util = commit->object.util; + struct util_info *util2 = other->object.util; + int *map = util->line_map; + int *map2 = util2->line_map; + int cur_chunk = 0; + int i1, i2; + + if (p->num && DEBUG) + print_patch(p); + + if (DEBUG) + printf("num lines 1: %d num lines 2: %d\n", util->num_lines, + util2->num_lines); + + for (i1 = 0, i2 = 0; i1 < util->num_lines; i1++, i2++) { + struct chunk *chunk = NULL; + if (cur_chunk < p->num) + chunk = &p->chunks[cur_chunk]; + + if (chunk && chunk->off1 == i1) { + if (DEBUG && i2 != chunk->off2) + printf("i2: %d off2: %d\n", i2, chunk->off2); + + assert(i2 == chunk->off2); + + i1--; + i2--; + if (chunk->len1 > 0) + i1 += chunk->len1; + + if (chunk->len2 > 0) + i2 += chunk->len2; + + cur_chunk++; + } else { + if (i2 >= util2->num_lines) + break; + + if (map[i1] != map2[i2] && map[i1] != -1) { + if (DEBUG) + printf("map: i1: %d %d %p i2: %d %d %p\n", + i1, map[i1], + i1 != -1 ? blame_lines[map[i1]] : NULL, + i2, map2[i2], + i2 != -1 ? blame_lines[map2[i2]] : NULL); + if (map2[i2] != -1 && + blame_lines[map[i1]] && + !blame_lines[map2[i2]]) + map[i1] = map2[i2]; + } + + if (map[i1] == -1 && map2[i2] != -1) + map[i1] = map2[i2]; + } + + if (DEBUG > 1) + printf("l1: %d l2: %d i1: %d i2: %d\n", + map[i1], map2[i2], i1, i2); + } +} + +static int map_line(struct commit *commit, int line) +{ + struct util_info *info = commit->object.util; + assert(line >= 0 && line < info->num_lines); + return info->line_map[line]; +} + +static int fill_util_info(struct commit *commit, const char *path) +{ + struct util_info *util; + if (commit->object.util) + return 0; + + util = xmalloc(sizeof(struct util_info)); + + if (get_blob_sha1(commit->tree, path, util->sha1)) { + free(util); + return 1; + } else { + util->buf = NULL; + util->size = 0; + util->line_map = NULL; + util->num_lines = -1; + commit->object.util = util; + return 0; + } +} + +static void alloc_line_map(struct commit *commit) +{ + struct util_info *util = commit->object.util; + int i; + + if (util->line_map) + return; + + get_blob(commit); + + util->num_lines = 0; + for (i = 0; i < util->size; i++) { + if (util->buf[i] == '\n') + util->num_lines++; + } + if(util->buf[util->size - 1] != '\n') + util->num_lines++; + + util->line_map = xmalloc(sizeof(int) * util->num_lines); + + for (i = 0; i < util->num_lines; i++) + util->line_map[i] = -1; +} + +static void init_first_commit(struct commit* commit, const char* filename) +{ + struct util_info* util; + int i; + + if (fill_util_info(commit, filename)) + die("fill_util_info failed"); + + alloc_line_map(commit); + + util = commit->object.util; + num_blame_lines = util->num_lines; + + for (i = 0; i < num_blame_lines; i++) + util->line_map[i] = i; +} + + +static void process_commits(struct rev_info *rev, const char *path, + struct commit** initial) +{ + int i; + struct util_info* util; + int lines_left; + int *blame_p; + int *new_lines; + int new_lines_len; + + struct commit* commit = get_revision(rev); + assert(commit); + init_first_commit(commit, path); + + util = commit->object.util; + num_blame_lines = util->num_lines; + blame_lines = xmalloc(sizeof(struct commit *) * num_blame_lines); + for (i = 0; i < num_blame_lines; i++) + blame_lines[i] = NULL; + + lines_left = num_blame_lines; + blame_p = xmalloc(sizeof(int) * num_blame_lines); + new_lines = xmalloc(sizeof(int) * num_blame_lines); + do { + struct commit_list *parents; + int num_parents; + struct util_info *util; + + if (DEBUG) + printf("\nProcessing commit: %d %s\n", num_commits, + sha1_to_hex(commit->object.sha1)); + + if (lines_left == 0) + return; + + num_commits++; + memset(blame_p, 0, sizeof(int) * num_blame_lines); + new_lines_len = 0; + num_parents = 0; + for (parents = commit->parents; + parents != NULL; parents = parents->next) + num_parents++; + + if(num_parents == 0) + *initial = commit; + + if(fill_util_info(commit, path)) + continue; + + alloc_line_map(commit); + util = commit->object.util; + + for (parents = commit->parents; + parents != NULL; parents = parents->next) { + struct commit *parent = parents->item; + struct patch *patch; + + if (parse_commit(parent) < 0) + die("parse_commit error"); + + if (DEBUG) + printf("parent: %s\n", + sha1_to_hex(parent->object.sha1)); + + if(fill_util_info(parent, path)) { + num_parents--; + continue; + } + + patch = get_patch(parent, commit); + alloc_line_map(parent); + fill_line_map(parent, commit, patch); + + for (i = 0; i < patch->num; i++) { + int l; + for (l = 0; l < patch->chunks[i].len2; l++) { + int mapped_line = + map_line(commit, patch->chunks[i].off2 + l); + if (mapped_line != -1) { + blame_p[mapped_line]++; + if (blame_p[mapped_line] == num_parents) + new_lines[new_lines_len++] = mapped_line; + } + } + } + free_patch(patch); + } + + if (DEBUG) + printf("parents: %d\n", num_parents); + + for (i = 0; i < new_lines_len; i++) { + int mapped_line = new_lines[i]; + if (blame_lines[mapped_line] == NULL) { + blame_lines[mapped_line] = commit; + lines_left--; + if (DEBUG) + printf("blame: mapped: %d i: %d\n", + mapped_line, i); + } + } + } while ((commit = get_revision(rev)) != NULL); +} + +int main(int argc, const char **argv) +{ + int i; + struct commit *initial = NULL; + unsigned char sha1[20]; + const char* filename; + int num_args; + const char* args[10]; + struct rev_info rev; + + setup_git_directory(); + + if (argc != 3) + die("Usage: blame commit-ish file"); + + + filename = argv[2]; + + { + struct commit* commit; + if (get_sha1(argv[1], sha1)) + die("get_sha1 failed"); + commit = lookup_commit_reference(sha1); + + if (fill_util_info(commit, filename)) { + printf("%s not found in %s\n", filename, argv[1]); + return 1; + } + } + + num_args = 0; + args[num_args++] = NULL; + args[num_args++] = "--topo-order"; + args[num_args++] = "--remove-empty"; + args[num_args++] = argv[1]; + args[num_args++] = "--"; + args[num_args++] = filename; + args[num_args] = NULL; + + setup_revisions(num_args, args, &rev, "HEAD"); + prepare_revision_walk(&rev); + process_commits(&rev, filename, &initial); + + for (i = 0; i < num_blame_lines; i++) { + struct commit *c = blame_lines[i]; + if (!c) + c = initial; + + printf("%d %.8s\n", i, sha1_to_hex(c->object.sha1)); +// printf("%d %s\n", i, find_unique_abbrev(blame_lines[i]->object.sha1, 6)); + } + + if (DEBUG) { + printf("num get patch: %d\n", num_get_patch); + printf("num commits: %d\n", num_commits); + printf("patch time: %f\n", patch_time / 1000000.0); + printf("initial: %s\n", sha1_to_hex(initial->object.sha1)); + } + + return 0; +} @@ -161,11 +161,13 @@ extern int hold_index_file_for_update(struct cache_file *, const char *path); extern int commit_index_file(struct cache_file *); extern void rollback_index_file(struct cache_file *); +/* Environment bits from configuration mechanism */ extern int trust_executable_bit; extern int assume_unchanged; extern int only_use_symrefs; extern int diff_rename_limit_default; extern int shared_repository; +extern const char *apply_default_whitespace; #define GIT_REPO_VERSION 0 extern int repository_format_version; @@ -352,4 +354,7 @@ extern int copy_fd(int ifd, int ofd); extern int receive_unpack_pack(int fd[2], const char *me, int quiet); extern int receive_keep_pack(int fd[2], const char *me, int quiet); +/* pager.c */ +extern void setup_pager(void); + #endif /* CACHE_H */ diff --git a/cat-file.c b/cat-file.c index 96d66b4304..1a613f3ee5 100644 --- a/cat-file.c +++ b/cat-file.c @@ -4,6 +4,92 @@ * Copyright (C) Linus Torvalds, 2005 */ #include "cache.h" +#include "exec_cmd.h" + +static void flush_buffer(const char *buf, unsigned long size) +{ + while (size > 0) { + long ret = xwrite(1, buf, size); + if (ret < 0) { + /* Ignore epipe */ + if (errno == EPIPE) + break; + die("git-cat-file: %s", strerror(errno)); + } else if (!ret) { + die("git-cat-file: disk full?"); + } + size -= ret; + buf += ret; + } +} + +static int pprint_tag(const unsigned char *sha1, const char *buf, unsigned long size) +{ + /* the parser in tag.c is useless here. */ + const char *endp = buf + size; + const char *cp = buf; + + while (cp < endp) { + char c = *cp++; + if (c != '\n') + continue; + if (7 <= endp - cp && !memcmp("tagger ", cp, 7)) { + const char *tagger = cp; + + /* Found the tagger line. Copy out the contents + * of the buffer so far. + */ + flush_buffer(buf, cp - buf); + + /* + * Do something intelligent, like pretty-printing + * the date. + */ + while (cp < endp) { + if (*cp++ == '\n') { + /* tagger to cp is a line + * that has ident and time. + */ + const char *sp = tagger; + char *ep; + unsigned long date; + long tz; + while (sp < cp && *sp != '>') + sp++; + if (sp == cp) { + /* give up */ + flush_buffer(tagger, + cp - tagger); + break; + } + while (sp < cp && + !('0' <= *sp && *sp <= '9')) + sp++; + flush_buffer(tagger, sp - tagger); + date = strtoul(sp, &ep, 10); + tz = strtol(ep, NULL, 10); + sp = show_date(date, tz); + flush_buffer(sp, strlen(sp)); + xwrite(1, "\n", 1); + break; + } + } + break; + } + if (cp < endp && *cp == '\n') + /* end of header */ + break; + } + /* At this point, we have copied out the header up to the end of + * the tagger line and cp points at one past \n. It could be the + * next header line after the tagger line, or it could be another + * \n that marks the end of the headers. We need to copy out the + * remainder as is. + */ + if (cp < endp) + flush_buffer(cp, endp - cp); + return 0; +} int main(int argc, char **argv) { @@ -15,7 +101,7 @@ int main(int argc, char **argv) setup_git_directory(); if (argc != 3 || get_sha1(argv[2], sha1)) - usage("git-cat-file [-t|-s|-e|<type>] <sha1>"); + usage("git-cat-file [-t|-s|-e|-p|<type>] <sha1>"); opt = 0; if ( argv[1][0] == '-' ) { @@ -43,6 +129,23 @@ int main(int argc, char **argv) case 'e': return !has_sha1_file(sha1); + case 'p': + if (get_sha1(argv[2], sha1) || + sha1_object_info(sha1, type, NULL)) + die("Not a valid object name %s", argv[2]); + + /* custom pretty-print here */ + if (!strcmp(type, "tree")) + return execl_git_cmd("ls-tree", argv[2], NULL); + + buf = read_sha1_file(sha1, type, &size); + if (!buf) + die("Cannot read object %s", argv[2]); + if (!strcmp(type, "tag")) + return pprint_tag(sha1, buf, size); + + /* otherwise just spit out the data */ + break; case 0: buf = read_object_with_reference(sha1, argv[1], &size, NULL); break; @@ -54,18 +157,6 @@ int main(int argc, char **argv) if (!buf) die("git-cat-file %s: bad file", argv[2]); - while (size > 0) { - long ret = xwrite(1, buf, size); - if (ret < 0) { - /* Ignore epipe */ - if (errno == EPIPE) - break; - die("git-cat-file: %s", strerror(errno)); - } else if (!ret) { - die("git-cat-file: disk full?"); - } - size -= ret; - buf += ret; - } + flush_buffer(buf, size); return 0; } diff --git a/contrib/git-svn/git-svn.perl b/contrib/git-svn/git-svn.perl index 0b7416526d..3c860e458c 100755 --- a/contrib/git-svn/git-svn.perl +++ b/contrib/git-svn/git-svn.perl @@ -4,19 +4,12 @@ use warnings; use strict; use vars qw/ $AUTHOR $VERSION - $SVN_URL $SVN_INFO $SVN_WC + $SVN_URL $SVN_INFO $SVN_WC $SVN_UUID $GIT_SVN_INDEX $GIT_SVN $GIT_DIR $REV_DIR/; $AUTHOR = 'Eric Wong <normalperson@yhbt.net>'; $VERSION = '0.10.0'; $GIT_DIR = $ENV{GIT_DIR} || "$ENV{PWD}/.git"; -$GIT_SVN = $ENV{GIT_SVN_ID} || 'git-svn'; -$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index"; -$ENV{GIT_DIR} ||= $GIT_DIR; -$SVN_URL = undef; -$REV_DIR = "$GIT_DIR/$GIT_SVN/revs"; -$SVN_WC = "$GIT_DIR/$GIT_SVN/tree"; - # make sure the svn binary gives consistent output between locales and TZs: $ENV{TZ} = 'UTC'; $ENV{LC_ALL} = 'C'; @@ -24,6 +17,7 @@ $ENV{LC_ALL} = 'C'; # If SVN:: library support is added, please make the dependencies # optional and preserve the capability to use the command-line client. # use eval { require SVN::... } to make it lazy load +# We don't use any modules not in the standard Perl distribution: use Carp qw/croak/; use IO::File qw//; use File::Basename qw/dirname basename/; @@ -32,27 +26,30 @@ use Getopt::Long qw/:config gnu_getopt no_ignore_case auto_abbrev/; use File::Spec qw//; use POSIX qw/strftime/; my $sha1 = qr/[a-f\d]{40}/; -my $sha1_short = qr/[a-f\d]{6,40}/; +my $sha1_short = qr/[a-f\d]{4,40}/; my ($_revision,$_stdin,$_no_ignore_ext,$_no_stop_copy,$_help,$_rmdir,$_edit, - $_find_copies_harder, $_l, $_version); - -GetOptions( 'revision|r=s' => \$_revision, - 'no-ignore-externals' => \$_no_ignore_ext, - 'stdin|' => \$_stdin, - 'edit|e' => \$_edit, - 'rmdir' => \$_rmdir, - 'help|H|h' => \$_help, - 'find-copies-harder' => \$_find_copies_harder, - 'l=i' => \$_l, - 'version|V' => \$_version, - 'no-stop-on-copy' => \$_no_stop_copy ); + $_find_copies_harder, $_l, $_version, $_upgrade, $_authors); +my (@_branch_from, %tree_map, %users); + +my %fc_opts = ( 'no-ignore-externals' => \$_no_ignore_ext, + 'branch|b=s' => \@_branch_from, + 'authors-file|A=s' => \$_authors ); my %cmd = ( - fetch => [ \&fetch, "Download new revisions from SVN" ], - init => [ \&init, "Initialize and fetch (import)"], - commit => [ \&commit, "Commit git revisions to SVN" ], - 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings" ], - rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)" ], - help => [ \&usage, "Show help" ], + fetch => [ \&fetch, "Download new revisions from SVN", + { 'revision|r=s' => \$_revision, %fc_opts } ], + init => [ \&init, "Initialize and fetch (import)", { } ], + commit => [ \&commit, "Commit git revisions to SVN", + { 'stdin|' => \$_stdin, + 'edit|e' => \$_edit, + 'rmdir' => \$_rmdir, + 'find-copies-harder' => \$_find_copies_harder, + 'l=i' => \$_l, + %fc_opts, + } ], + 'show-ignore' => [ \&show_ignore, "Show svn:ignore listings", { } ], + rebuild => [ \&rebuild, "Rebuild git-svn metadata (after git clone)", + { 'no-ignore-externals' => \$_no_ignore_ext, + 'upgrade' => \$_upgrade } ], ); my $cmd; for (my $i = 0; $i < @ARGV; $i++) { @@ -63,16 +60,23 @@ for (my $i = 0; $i < @ARGV; $i++) { } }; -# we may be called as git-svn-(command), or git-svn(command). -foreach (keys %cmd) { - if (/git\-svn\-?($_)(?:\.\w+)?$/) { - $cmd = $1; - last; - } -} +my %opts = %{$cmd{$cmd}->[2]} if (defined $cmd); + +GetOptions(%opts, 'help|H|h' => \$_help, + 'version|V' => \$_version, + 'id|i=s' => \$GIT_SVN) or exit 1; + +$GIT_SVN ||= $ENV{GIT_SVN_ID} || 'git-svn'; +$GIT_SVN_INDEX = "$GIT_DIR/$GIT_SVN/index"; +$ENV{GIT_DIR} ||= $GIT_DIR; +$SVN_URL = undef; +$REV_DIR = "$GIT_DIR/$GIT_SVN/revs"; +$SVN_WC = "$GIT_DIR/$GIT_SVN/tree"; + usage(0) if $_help; version() if $_version; -usage(1) unless (defined $cmd); +usage(1) unless defined $cmd; +load_authors() if $_authors; svn_check_ignore_externals(); $cmd{$cmd}->[0]->(@ARGV); exit 0; @@ -84,15 +88,25 @@ sub usage { print $fd <<""; git-svn - bidirectional operations between a single Subversion tree and git Usage: $0 <command> [options] [arguments]\n -Available commands: + + print $fd "Available commands:\n" unless $cmd; foreach (sort keys %cmd) { - print $fd ' ',pack('A10',$_),$cmd{$_}->[1],"\n"; + next if $cmd && $cmd ne $_; + print $fd ' ',pack('A13',$_),$cmd{$_}->[1],"\n"; + foreach (keys %{$cmd{$_}->[2]}) { + # prints out arguments as they should be passed: + my $x = s#=s$## ? '<arg>' : s#=i$## ? '<num>' : ''; + print $fd ' ' x 17, join(', ', map { length $_ > 1 ? + "--$_" : "-$_" } + split /\|/,$_)," $x\n"; + } } print $fd <<""; -\nGIT_SVN_ID may be set in the environment to an arbitrary identifier if -you're tracking multiple SVN branches/repositories in one git repository -and want to keep them separate. +\nGIT_SVN_ID may be set in the environment or via the --id/-i switch to an +arbitrary identifier if you're tracking multiple SVN branches/repositories in +one git repository and want to keep them separate. See git-svn(1) for more +information. exit $exit; } @@ -104,15 +118,19 @@ sub version { sub rebuild { $SVN_URL = shift or undef; - my $repo_uuid; my $newest_rev = 0; + if ($_upgrade) { + sys('git-update-ref',"refs/remotes/$GIT_SVN","$GIT_SVN-HEAD"); + } else { + check_upgrade_needed(); + } my $pid = open(my $rev_list,'-|'); defined $pid or croak $!; if ($pid == 0) { - exec("git-rev-list","$GIT_SVN-HEAD") or croak $!; + exec("git-rev-list","refs/remotes/$GIT_SVN") or croak $!; } - my $first; + my $latest; while (<$rev_list>) { chomp; my $c = $_; @@ -132,18 +150,20 @@ sub rebuild { "$c, $id\n"; } } + + # if we merged or otherwise started elsewhere, this is + # how we break out of it + next if (defined $SVN_UUID && ($uuid ne $SVN_UUID)); + next if (defined $SVN_URL && ($url ne $SVN_URL)); + print "r$rev = $c\n"; - unless (defined $first) { + unless (defined $latest) { if (!$SVN_URL && !$url) { croak "SVN repository location required: $url\n"; } $SVN_URL ||= $url; - $repo_uuid = setup_git_svn(); - $first = $rev; - } - if ($uuid ne $repo_uuid) { - croak "Repository UUIDs do not match!\ngot: $uuid\n", - "expected: $repo_uuid\n"; + $SVN_UUID ||= setup_git_svn(); + $latest = $rev; } assert_revision_eq_or_unknown($rev, $c); sys('git-update-ref',"$GIT_SVN/revs/$rev",$c); @@ -151,7 +171,7 @@ sub rebuild { } close $rev_list or croak $?; if (!chdir $SVN_WC) { - my @svn_co = ('svn','co',"-r$first"); + my @svn_co = ('svn','co',"-r$latest"); push @svn_co, '--ignore-externals' unless $_no_ignore_ext; sys(@svn_co, $SVN_URL, $SVN_WC); chdir $SVN_WC or croak $!; @@ -168,6 +188,13 @@ sub rebuild { exec('git-write-tree'); } waitpid $pid, 0; + + if ($_upgrade) { + print STDERR <<""; +Keeping deprecated refs/head/$GIT_SVN-HEAD for now. Please remove it +when you have upgraded your tools and habits to use refs/remotes/$GIT_SVN + + } } sub init { @@ -180,6 +207,7 @@ sub init { sub fetch { my (@parents) = @_; + check_upgrade_needed(); $SVN_URL ||= file_to_s("$GIT_DIR/$GIT_SVN/info/url"); my @log_args = -d $SVN_WC ? ($SVN_WC) : ($SVN_URL); unless ($_revision) { @@ -199,9 +227,6 @@ sub fetch { sys(@svn_co, $SVN_URL, $SVN_WC); chdir $SVN_WC or croak $!; $last_commit = git_commit($base, @parents); - unless (-f "$GIT_DIR/refs/heads/master") { - sys(qw(git-update-ref refs/heads/master),$last_commit); - } assert_svn_wc_clean($base->{revision}, $last_commit); } else { chdir $SVN_WC or croak $!; @@ -217,16 +242,20 @@ sub fetch { $last_commit = git_commit($log_msg, $last_commit, @parents); } assert_svn_wc_clean($last_rev, $last_commit); + unless (-e "$GIT_DIR/refs/heads/master") { + sys(qw(git-update-ref refs/heads/master),$last_commit); + } return pop @$svn_log; } sub commit { my (@commits) = @_; + check_upgrade_needed(); if ($_stdin || !@commits) { print "Reading from stdin...\n"; @commits = (); while (<STDIN>) { - if (/\b([a-f\d]{6,40})\b/) { + if (/\b($sha1_short)\b/o) { unshift @commits, $1; } } @@ -248,7 +277,6 @@ sub commit { chdir $SVN_WC or croak $!; my $svn_current_rev = svn_info('.')->{'Last Changed Rev'}; foreach my $c (@revs) { - print "Committing $c\n"; my $mods = svn_checkout_tree($svn_current_rev, $c); if (scalar @$mods == 0) { print "Skipping, no changes detected\n"; @@ -295,24 +323,31 @@ sub setup_git_svn { mkpath(["$GIT_DIR/$GIT_SVN/info"]); mkpath([$REV_DIR]); s_to_file($SVN_URL,"$GIT_DIR/$GIT_SVN/info/url"); - my $uuid = svn_info($SVN_URL)->{'Repository UUID'} or + $SVN_UUID = svn_info($SVN_URL)->{'Repository UUID'} or croak "Repository UUID unreadable\n"; - s_to_file($uuid,"$GIT_DIR/$GIT_SVN/info/uuid"); + s_to_file($SVN_UUID,"$GIT_DIR/$GIT_SVN/info/uuid"); open my $fd, '>>', "$GIT_DIR/$GIT_SVN/info/exclude" or croak $!; print $fd '.svn',"\n"; close $fd or croak $!; - return $uuid; + return $SVN_UUID; } sub assert_svn_wc_clean { my ($svn_rev, $treeish) = @_; croak "$svn_rev is not an integer!\n" unless ($svn_rev =~ /^\d+$/); croak "$treeish is not a sha1!\n" unless ($treeish =~ /^$sha1$/o); - my $svn_info = svn_info('.'); - if ($svn_rev != $svn_info->{'Last Changed Rev'}) { - croak "Expected r$svn_rev, got r", - $svn_info->{'Last Changed Rev'},"\n"; + my $lcr = svn_info('.')->{'Last Changed Rev'}; + if ($svn_rev != $lcr) { + print STDERR "Checking for copy-tree ... "; + # use + my @diff = grep(/^Index: /,(safe_qx(qw(svn diff), + "-r$lcr:$svn_rev"))); + if (@diff) { + croak "Nope! Expected r$svn_rev, got r$lcr\n"; + } else { + print STDERR "OK!\n"; + } } my @status = grep(!/^Performing status on external/,(`svn status`)); @status = grep(!/^\s*$/,@status); @@ -495,7 +530,7 @@ sub svn_checkout_tree { my ($svn_rev, $treeish) = @_; my $from = file_to_s("$REV_DIR/$svn_rev"); assert_svn_wc_clean($svn_rev,$from); - print "diff-tree '$from' '$treeish'\n"; + print "diff-tree $from $treeish\n"; my $pid = open my $diff_fh, '-|'; defined $pid or croak $!; if ($pid == 0) { @@ -506,7 +541,7 @@ sub svn_checkout_tree { } my $mods = parse_diff_tree($diff_fh); unless (@$mods) { - # git can do empty commits, SVN doesn't allow it... + # git can do empty commits, but SVN doesn't allow it... return $mods; } my ($rm, $add) = precommit_check($mods); @@ -593,7 +628,7 @@ sub svn_commit_tree { my ($svn_rev, $commit) = @_; my $commit_msg = "$GIT_DIR/$GIT_SVN/.svn-commit.tmp.$$"; my %log_msg = ( msg => '' ); - open my $msg, '>', $commit_msg or croak $!; + open my $msg, '>', $commit_msg or croak $!; chomp(my $type = `git-cat-file -t $commit`); if ($type eq 'commit') { @@ -607,8 +642,10 @@ sub svn_commit_tree { while (<$msg_fh>) { if (!$in_msg) { $in_msg = 1 if (/^\s*$/); + } elsif (/^git-svn-id: /) { + # skip this, we regenerate the correct one + # on re-fetch anyways } else { - $log_msg{msg} .= $_; print $msg $_ or croak $!; } } @@ -620,6 +657,15 @@ sub svn_commit_tree { my $editor = $ENV{VISUAL} || $ENV{EDITOR} || 'vi'; system($editor, $commit_msg); } + + # file_to_s removes all trailing newlines, so just use chomp() here: + open $msg, '<', $commit_msg or croak $!; + { local $/; chomp($log_msg{msg} = <$msg>); } + close $msg or croak $!; + + my ($oneline) = ($log_msg{msg} =~ /([^\n\r]+)/); + print "Committing $commit: $oneline\n"; + my @ci_output = safe_qx(qw(svn commit -F),$commit_msg); my ($committed) = grep(/^Committed revision \d+\./,@ci_output); unlink $commit_msg; @@ -711,6 +757,10 @@ sub svn_log_raw { author => $author, lines => $lines, msg => '' ); + if (defined $_authors && ! defined $users{$author}) { + die "Author: $author not defined in ", + "$_authors file\n"; + } push @svn_log, \%log_msg; $state = 'msg_start'; next; @@ -810,9 +860,9 @@ sub git_commit { my ($log_msg, @parents) = @_; assert_revision_unknown($log_msg->{revision}); my $out_fh = IO::File->new_tmpfile or croak $!; - my $info = svn_info('.'); - my $uuid = $info->{'Repository UUID'}; - defined $uuid or croak "Unable to get Repository UUID\n"; + $SVN_UUID ||= svn_info('.')->{'Repository UUID'}; + + map_tree_joins() if (@_branch_from && !%tree_map); # commit parents can be conditionally bound to a particular # svn revision via: "svn_revno=commit_sha1", filter them out here: @@ -835,19 +885,26 @@ sub git_commit { git_addremove(); chomp(my $tree = `git-write-tree`); croak if $?; + if (exists $tree_map{$tree}) { + my %seen_parent = map { $_ => 1 } @exec_parents; + foreach (@{$tree_map{$tree}}) { + # MAXPARENT is defined to 16 in commit-tree.c: + if ($seen_parent{$_} || @exec_parents > 16) { + next; + } + push @exec_parents, $_; + $seen_parent{$_} = 1; + } + } my $msg_fh = IO::File->new_tmpfile or croak $!; print $msg_fh $log_msg->{msg}, "\ngit-svn-id: ", "$SVN_URL\@$log_msg->{revision}", - " $uuid\n" or croak $!; + " $SVN_UUID\n" or croak $!; $msg_fh->flush == 0 or croak $!; seek $msg_fh, 0, 0 or croak $!; - $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = - $log_msg->{author}; - $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = - $log_msg->{author}."\@$uuid"; - $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = - $log_msg->{date}; + set_commit_env($log_msg); + my @exec = ('git-commit-tree',$tree); push @exec, '-p', $_ foreach @exec_parents; open STDIN, '<&', $msg_fh or croak $!; @@ -863,7 +920,7 @@ sub git_commit { if ($commit !~ /^$sha1$/o) { croak "Failed to commit, invalid sha1: $commit\n"; } - my @update_ref = ('git-update-ref',"refs/heads/$GIT_SVN-HEAD",$commit); + my @update_ref = ('git-update-ref',"refs/remotes/$GIT_SVN",$commit); if (my $primary_parent = shift @exec_parents) { push @update_ref, $primary_parent; } @@ -873,6 +930,16 @@ sub git_commit { return $commit; } +sub set_commit_env { + my ($log_msg) = @_; + my $author = $log_msg->{author}; + my ($name,$email) = defined $users{$author} ? @{$users{$author}} + : ($author,"$author\@$SVN_UUID"); + $ENV{GIT_AUTHOR_NAME} = $ENV{GIT_COMMITTER_NAME} = $name; + $ENV{GIT_AUTHOR_EMAIL} = $ENV{GIT_COMMITTER_EMAIL} = $email; + $ENV{GIT_AUTHOR_DATE} = $ENV{GIT_COMMITTER_DATE} = $log_msg->{date}; +} + sub apply_mod_line_blob { my $m = shift; if ($m->{mode_b} =~ /^120/) { @@ -936,6 +1003,63 @@ sub svn_check_ignore_externals { $_no_ignore_ext = 1; } } + +sub check_upgrade_needed { + my $old = eval { + my $pid = open my $child, '-|'; + defined $pid or croak $!; + if ($pid == 0) { + close STDERR; + exec('git-rev-parse',"$GIT_SVN-HEAD") or croak $?; + } + my @ret = (<$child>); + close $child or croak $?; + die $? if $?; # just in case close didn't error out + return wantarray ? @ret : join('',@ret); + }; + return unless $old; + my $head = eval { safe_qx('git-rev-parse',"refs/remotes/$GIT_SVN") }; + if ($@ || !$head) { + print STDERR "Please run: $0 rebuild --upgrade\n"; + exit 1; + } +} + +# fills %tree_map with a reverse mapping of trees to commits. Useful +# for finding parents to commit on. +sub map_tree_joins { + foreach my $br (@_branch_from) { + my $pid = open my $pipe, '-|'; + defined $pid or croak $!; + if ($pid == 0) { + exec(qw(git-rev-list --pretty=raw), $br) or croak $?; + } + while (<$pipe>) { + if (/^commit ($sha1)$/o) { + my $commit = $1; + my ($tree) = (<$pipe> =~ /^tree ($sha1)$/o); + unless (defined $tree) { + die "Failed to parse commit $commit\n"; + } + push @{$tree_map{$tree}}, $commit; + } + } + close $pipe or croak $?; + } +} + +# '<svn username> = real-name <email address>' mapping based on git-svnimport: +sub load_authors { + open my $authors, '<', $_authors or die "Can't open $_authors $!\n"; + while (<$authors>) { + chomp; + next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; + my ($user, $name, $email) = ($1, $2, $3); + $users{$user} = [$name, $email]; + } + close $authors or croak $!; +} + __END__ Data structures: @@ -960,7 +1084,7 @@ diff-index line ($m hash) mode_a => first column of diff-index output, no leading ':', mode_b => second column of diff-index output, sha1_b => sha1sum of the final blob, - chg => change type [MCRAD], + chg => change type [MCRADT], file_a => original file name of a file (iff chg is 'C' or 'R') file_b => new/current file name of a file (any chg) } diff --git a/contrib/git-svn/git-svn.txt b/contrib/git-svn/git-svn.txt index b29073997c..8e9a971a85 100644 --- a/contrib/git-svn/git-svn.txt +++ b/contrib/git-svn/git-svn.txt @@ -27,7 +27,7 @@ For importing svn, git-svnimport is potentially more powerful when operating on repositories organized under the recommended trunk/branch/tags structure, and should be faster, too. -git-svn completely ignores the very limited view of branching that +git-svn mostly ignores the very limited view of branching that Subversion has. This allows git-svn to be much easier to use, especially on repositories that are not organized in a manner that git-svnimport is designed for. @@ -41,12 +41,12 @@ init:: fetch:: Fetch unfetched revisions from the SVN_URL we are tracking. - refs/heads/git-svn-HEAD will be updated to the latest revision. + refs/heads/remotes/git-svn will be updated to the latest revision. - Note: You should never attempt to modify the git-svn-HEAD branch - outside of git-svn. Instead, create a branch from git-svn-HEAD + Note: You should never attempt to modify the remotes/git-svn branch + outside of git-svn. Instead, create a branch from remotes/git-svn and work on that branch. Use the 'commit' command (see below) - to write git commits back to git-svn-HEAD. + to write git commits back to remotes/git-svn. commit:: Commit specified commit or tree objects to SVN. This relies on @@ -116,8 +116,38 @@ OPTIONS They are both passed directly to git-diff-tree see git-diff-tree(1) for more information. +ADVANCED OPTIONS +---------------- +-b<refname>:: +--branch <refname>:: + Used with 'fetch' or 'commit'. + + This can be used to join arbitrary git branches to remotes/git-svn + on new commits where the tree object is equivalent. + + When used with different GIT_SVN_ID values, tags and branches in + SVN can be tracked this way, as can some merges where the heads + end up having completely equivalent content. This can even be + used to track branches across multiple SVN _repositories_. + + This option may be specified multiple times, once for each + branch. + +-i<GIT_SVN_ID>:: +--id <GIT_SVN_ID>:: + This sets GIT_SVN_ID (instead of using the environment). See + the section on "Tracking Multiple Repositories or Branches" for + more information on using GIT_SVN_ID. + COMPATIBILITY OPTIONS --------------------- +--upgrade:: + Only used with the 'rebuild' command. + + Run this if you used an old version of git-svn that used + 'git-svn-HEAD' instead of 'remotes/git-svn' as the branch + for tracking the remote. + --no-ignore-externals:: Only used with the 'fetch' and 'rebuild' command. @@ -155,14 +185,14 @@ Tracking and contributing to an Subversion managed-project: # Fetch remote revisions:: git-svn fetch # Create your own branch to hack on:: - git checkout -b my-branch git-svn-HEAD + git checkout -b my-branch remotes/git-svn # Commit only the git commits you want to SVN:: git-svn commit <tree-ish> [<tree-ish_2> ...] # Commit all the git commits from my-branch that don't exist in SVN:: - git-svn commit git-svn-HEAD..my-branch + git-svn commit remotes/git-svn..my-branch # Something is committed to SVN, pull the latest into your branch:: - git-svn fetch && git pull . git-svn-HEAD -# Append svn:ignore settings to the default git exclude file: + git-svn fetch && git pull . remotes/git-svn +# Append svn:ignore settings to the default git exclude file:: git-svn show-ignore >> .git/info/exclude DESIGN PHILOSOPHY @@ -184,8 +214,8 @@ SVN repositories via one git repository. Simply set the GIT_SVN_ID environment variable to a name other other than "git-svn" (the default) and git-svn will ignore the contents of the $GIT_DIR/git-svn directory and instead do all of its work in $GIT_DIR/$GIT_SVN_ID for that -invocation. The interface branch will be $GIT_SVN_ID-HEAD, instead of -git-svn-HEAD. Any $GIT_SVN_ID-HEAD branch should never be modified +invocation. The interface branch will be remotes/$GIT_SVN_ID, instead of +remotes/git-svn. Any remotes/$GIT_SVN_ID branch should never be modified by the user outside of git-svn commands. ADDITIONAL FETCH ARGUMENTS diff --git a/contrib/git-svn/t/t0000-contrib-git-svn.sh b/contrib/git-svn/t/t0000-contrib-git-svn.sh index 181dfe008b..80ad3573db 100644 --- a/contrib/git-svn/t/t0000-contrib-git-svn.sh +++ b/contrib/git-svn/t/t0000-contrib-git-svn.sh @@ -71,14 +71,14 @@ test_expect_success \ name='try a deep --rmdir with a commit' -git checkout -b mybranch git-svn-HEAD +git checkout -b mybranch remotes/git-svn mv dir/a/b/c/d/e/file dir/file cp dir/file file git update-index --add --remove dir/a/b/c/d/e/file dir/file file git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch && test -d $SVN_TREE/dir && test ! -d $SVN_TREE/dir/a" @@ -91,13 +91,13 @@ git update-index --add dir/file/file git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch' \ || true name='detect node change from directory to file #1' rm -rf dir $GIT_DIR/index -git checkout -b mybranch2 git-svn-HEAD +git checkout -b mybranch2 remotes/git-svn mv bar/zzz zzz rm -rf bar mv zzz bar @@ -106,13 +106,13 @@ git update-index --add -- bar git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch2' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch2' \ || true name='detect node change from file to directory #2' rm -f $GIT_DIR/index -git checkout -b mybranch3 git-svn-HEAD +git checkout -b mybranch3 remotes/git-svn rm bar/zzz git-update-index --remove bar/zzz mkdir bar/zzz @@ -121,13 +121,13 @@ git-update-index --add bar/zzz/yyy git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch3' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch3' \ || true name='detect node change from directory to file #2' rm -f $GIT_DIR/index -git checkout -b mybranch4 git-svn-HEAD +git checkout -b mybranch4 remotes/git-svn rm -rf dir git update-index --remove -- dir/file touch dir @@ -136,19 +136,19 @@ git update-index --add -- dir git commit -m "$name" test_expect_code 1 "$name" \ - 'git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch4' \ + 'git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch4' \ || true name='remove executable bit from a file' rm -f $GIT_DIR/index -git checkout -b mybranch5 git-svn-HEAD +git checkout -b mybranch5 remotes/git-svn chmod -x exec.sh git update-index exec.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test ! -x $SVN_TREE/exec.sh" @@ -158,7 +158,7 @@ git update-index exec.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -x $SVN_TREE/exec.sh" @@ -170,7 +170,7 @@ git update-index exec.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -L $SVN_TREE/exec.sh" @@ -182,7 +182,7 @@ git update-index --add bar/zzz exec-2.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -x $SVN_TREE/bar/zzz && test -L $SVN_TREE/exec-2.sh" @@ -196,7 +196,7 @@ git update-index exec-2.sh git commit -m "$name" test_expect_success "$name" \ - "git-svn commit --find-copies-harder --rmdir git-svn-HEAD..mybranch5 && + "git-svn commit --find-copies-harder --rmdir remotes/git-svn..mybranch5 && test -f $SVN_TREE/exec-2.sh && test ! -L $SVN_TREE/exec-2.sh && diff -u help $SVN_TREE/exec-2.sh" @@ -207,9 +207,9 @@ name='test fetch functionality (svn => git) with alternate GIT_SVN_ID' GIT_SVN_ID=alt export GIT_SVN_ID test_expect_success "$name" \ - "git-svn init $svnrepo && git-svn fetch -v && - git-rev-list --pretty=raw git-svn-HEAD | grep ^tree | uniq > a && - git-rev-list --pretty=raw alt-HEAD | grep ^tree | uniq > b && + "git-svn init $svnrepo && git-svn fetch && + git-rev-list --pretty=raw remotes/git-svn | grep ^tree | uniq > a && + git-rev-list --pretty=raw remotes/alt | grep ^tree | uniq > b && diff -u a b" test_done diff --git a/contrib/gitview/gitview b/contrib/gitview/gitview index ea05cd4240..781badbc5b 100755 --- a/contrib/gitview/gitview +++ b/contrib/gitview/gitview @@ -513,7 +513,7 @@ class GitView: scrollwin = gtk.ScrolledWindow() - scrollwin.set_policy(gtk.POLICY_NEVER, gtk.POLICY_AUTOMATIC) + scrollwin.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC) scrollwin.set_shadow_type(gtk.SHADOW_IN) vbox.pack_start(scrollwin, expand=True, fill=True) scrollwin.show() @@ -526,9 +526,6 @@ class GitView: self.treeview.show() cell = CellRendererGraph() - # Set the default width to 265 - # This make sure that we have nice display with large tag names - cell.set_property("width", 265) column = gtk.TreeViewColumn() column.set_resizable(True) column.pack_start(cell, expand=True) @@ -801,7 +798,7 @@ class GitView: button.set_relief(gtk.RELIEF_NONE) button.set_sensitive(True) button.connect("clicked", self._show_clicked_cb, - child_id, commit.commit_sha1) + child_id, commit.commit_sha1, self.encoding) hbox.pack_start(button, expand=False, fill=True) button.show() diff --git a/count-delta.c b/count-delta.c deleted file mode 100644 index 058a2aadb1..0000000000 --- a/count-delta.c +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (C) 2005 Junio C Hamano - * The delta-parsing part is almost straight copy of patch-delta.c - * which is (C) 2005 Nicolas Pitre <nico@cam.org>. - */ -#include <stdlib.h> -#include <string.h> -#include <limits.h> -#include "delta.h" -#include "count-delta.h" - -/* - * NOTE. We do not _interpret_ delta fully. As an approximation, we - * just count the number of bytes that are copied from the source, and - * the number of literal data bytes that are inserted. - * - * Number of bytes that are _not_ copied from the source is deletion, - * and number of inserted literal bytes are addition, so sum of them - * is the extent of damage. - */ -int count_delta(void *delta_buf, unsigned long delta_size, - unsigned long *src_copied, unsigned long *literal_added) -{ - unsigned long copied_from_source, added_literal; - const unsigned char *data, *top; - unsigned char cmd; - unsigned long src_size, dst_size, out; - - if (delta_size < DELTA_SIZE_MIN) - return -1; - - data = delta_buf; - top = delta_buf + delta_size; - - src_size = get_delta_hdr_size(&data); - dst_size = get_delta_hdr_size(&data); - - added_literal = copied_from_source = out = 0; - while (data < top) { - cmd = *data++; - if (cmd & 0x80) { - unsigned long cp_off = 0, cp_size = 0; - if (cmd & 0x01) cp_off = *data++; - if (cmd & 0x02) cp_off |= (*data++ << 8); - if (cmd & 0x04) cp_off |= (*data++ << 16); - if (cmd & 0x08) cp_off |= (*data++ << 24); - if (cmd & 0x10) cp_size = *data++; - if (cmd & 0x20) cp_size |= (*data++ << 8); - if (cmd & 0x40) cp_size |= (*data++ << 16); - if (cp_size == 0) cp_size = 0x10000; - - copied_from_source += cp_size; - out += cp_size; - } else { - /* write literal into dst */ - added_literal += cmd; - out += cmd; - data += cmd; - } - } - - /* sanity check */ - if (data != top || out != dst_size) - return -1; - - /* delete size is what was _not_ copied from source. - * edit size is that and literal additions. - */ - *src_copied = copied_from_source; - *literal_added = added_literal; - return 0; -} diff --git a/count-delta.h b/count-delta.h deleted file mode 100644 index 7359629827..0000000000 --- a/count-delta.h +++ /dev/null @@ -1,10 +0,0 @@ -/* - * Copyright (C) 2005 Junio C Hamano - */ -#ifndef COUNT_DELTA_H -#define COUNT_DELTA_H - -int count_delta(void *, unsigned long, - unsigned long *src_copied, unsigned long *literal_added); - -#endif diff --git a/diff-delta.c b/diff-delta.c index c2f656ae39..2ed5984b1c 100644 --- a/diff-delta.c +++ b/diff-delta.c @@ -19,8 +19,9 @@ */ #include <stdlib.h> +#include <string.h> +#include <zlib.h> #include "delta.h" -#include "zlib.h" /* block size: min = 16, max = 64k, power of 2 */ @@ -29,149 +30,87 @@ #define MIN(a, b) ((a) < (b) ? (a) : (b)) #define GR_PRIME 0x9e370001 -#define HASH(v, b) (((unsigned int)(v) * GR_PRIME) >> (32 - (b))) - -static unsigned int hashbits(unsigned int size) -{ - unsigned int val = 1, bits = 0; - while (val < size && bits < 32) { - val <<= 1; - bits++; - } - return bits ? bits: 1; -} - -typedef struct s_chanode { - struct s_chanode *next; - int icurr; -} chanode_t; - -typedef struct s_chastore { - int isize, nsize; - chanode_t *ancur; -} chastore_t; - -static void cha_init(chastore_t *cha, int isize, int icount) -{ - cha->isize = isize; - cha->nsize = icount * isize; - cha->ancur = NULL; -} - -static void *cha_alloc(chastore_t *cha) -{ - chanode_t *ancur; - void *data; +#define HASH(v, shift) (((unsigned int)(v) * GR_PRIME) >> (shift)) - ancur = cha->ancur; - if (!ancur || ancur->icurr == cha->nsize) { - ancur = malloc(sizeof(chanode_t) + cha->nsize); - if (!ancur) - return NULL; - ancur->icurr = 0; - ancur->next = cha->ancur; - cha->ancur = ancur; - } - - data = (void *)ancur + sizeof(chanode_t) + ancur->icurr; - ancur->icurr += cha->isize; - return data; -} - -static void cha_free(chastore_t *cha) -{ - chanode_t *cur = cha->ancur; - while (cur) { - chanode_t *tmp = cur; - cur = cur->next; - free(tmp); - } -} - -typedef struct s_bdrecord { - struct s_bdrecord *next; - unsigned int fp; +struct index { const unsigned char *ptr; -} bdrecord_t; - -typedef struct s_bdfile { - chastore_t cha; - unsigned int fphbits; - bdrecord_t **fphash; -} bdfile_t; + unsigned int val; + struct index *next; +}; -static int delta_prepare(const unsigned char *buf, int bufsize, bdfile_t *bdf) +static struct index ** delta_index(const unsigned char *buf, + unsigned long bufsize, + unsigned int *hash_shift) { - unsigned int fphbits; - int i, hsize; - const unsigned char *data, *top; - bdrecord_t *brec; - bdrecord_t **fphash; - - fphbits = hashbits(bufsize / BLK_SIZE + 1); - hsize = 1 << fphbits; - fphash = malloc(hsize * sizeof(bdrecord_t *)); - if (!fphash) - return -1; - for (i = 0; i < hsize; i++) - fphash[i] = NULL; - cha_init(&bdf->cha, sizeof(bdrecord_t), hsize / 4 + 1); - - top = buf + bufsize; - data = buf + (bufsize / BLK_SIZE) * BLK_SIZE; - if (data == top) + unsigned int hsize, hshift, entries, blksize, i; + const unsigned char *data; + struct index *entry, **hash; + void *mem; + + /* determine index hash size */ + entries = (bufsize + BLK_SIZE - 1) / BLK_SIZE; + hsize = entries / 4; + for (i = 4; (1 << i) < hsize && i < 16; i++); + hsize = 1 << i; + hshift = 32 - i; + *hash_shift = hshift; + + /* allocate lookup index */ + mem = malloc(hsize * sizeof(*hash) + entries * sizeof(*entry)); + if (!mem) + return NULL; + hash = mem; + entry = mem + hsize * sizeof(*hash); + memset(hash, 0, hsize * sizeof(*hash)); + + /* then populate it */ + data = buf + entries * BLK_SIZE - BLK_SIZE; + blksize = bufsize - (data - buf); + while (data >= buf) { + unsigned int val = adler32(0, data, blksize); + i = HASH(val, hshift); + entry->ptr = data; + entry->val = val; + entry->next = hash[i]; + hash[i] = entry++; + blksize = BLK_SIZE; data -= BLK_SIZE; + } - for ( ; data >= buf; data -= BLK_SIZE) { - brec = cha_alloc(&bdf->cha); - if (!brec) { - cha_free(&bdf->cha); - free(fphash); - return -1; - } - brec->fp = adler32(0, data, MIN(BLK_SIZE, top - data)); - brec->ptr = data; - i = HASH(brec->fp, fphbits); - brec->next = fphash[i]; - fphash[i] = brec; - } - - bdf->fphbits = fphbits; - bdf->fphash = fphash; - - return 0; -} - -static void delta_cleanup(bdfile_t *bdf) -{ - free(bdf->fphash); - cha_free(&bdf->cha); + return hash; } +/* provide the size of the copy opcode given the block offset and size */ #define COPYOP_SIZE(o, s) \ (!!(o & 0xff) + !!(o & 0xff00) + !!(o & 0xff0000) + !!(o & 0xff000000) + \ !!(s & 0xff) + !!(s & 0xff00) + 1) +/* the maximum size for any opcode */ +#define MAX_OP_SIZE COPYOP_SIZE(0xffffffff, 0xffffffff) + void *diff_delta(void *from_buf, unsigned long from_size, void *to_buf, unsigned long to_size, unsigned long *delta_size, unsigned long max_size) { - int i, outpos, outsize, inscnt, csize, msize, moff; - unsigned int fp; - const unsigned char *ref_data, *ref_top, *data, *top, *ptr1, *ptr2; - unsigned char *out, *orig; - bdrecord_t *brec; - bdfile_t bdf; + unsigned int i, outpos, outsize, inscnt, hash_shift; + const unsigned char *ref_data, *ref_top, *data, *top; + unsigned char *out; + struct index *entry, **hash; - if (!from_size || !to_size || delta_prepare(from_buf, from_size, &bdf)) + if (!from_size || !to_size) + return NULL; + hash = delta_index(from_buf, from_size, &hash_shift); + if (!hash) return NULL; - + outpos = 0; outsize = 8192; + if (max_size && outsize >= max_size) + outsize = max_size + MAX_OP_SIZE + 1; out = malloc(outsize); if (!out) { - delta_cleanup(&bdf); + free(hash); return NULL; } @@ -199,28 +138,32 @@ void *diff_delta(void *from_buf, unsigned long from_size, } inscnt = 0; - moff = 0; - while (data < top) { - msize = 0; - fp = adler32(0, data, MIN(top - data, BLK_SIZE)); - i = HASH(fp, bdf.fphbits); - for (brec = bdf.fphash[i]; brec; brec = brec->next) { - if (brec->fp == fp) { - csize = ref_top - brec->ptr; - if (csize > top - data) - csize = top - data; - for (ptr1 = brec->ptr, ptr2 = data; - csize && *ptr1 == *ptr2; - csize--, ptr1++, ptr2++); - csize = ptr1 - brec->ptr; - if (csize > msize) { - moff = brec->ptr - ref_data; - msize = csize; - if (msize >= 0x10000) { - msize = 0x10000; - break; - } + while (data < top) { + unsigned int moff = 0, msize = 0; + unsigned int blksize = MIN(top - data, BLK_SIZE); + unsigned int val = adler32(0, data, blksize); + i = HASH(val, hash_shift); + for (entry = hash[i]; entry; entry = entry->next) { + const unsigned char *ref = entry->ptr; + const unsigned char *src = data; + unsigned int ref_size = ref_top - ref; + if (entry->val != val) + continue; + if (ref_size > top - src) + ref_size = top - src; + while (ref_size && *src++ == *ref) { + ref++; + ref_size--; + } + ref_size = ref - entry->ptr; + if (ref_size > msize) { + /* this is our best match so far */ + moff = entry->ptr - ref_data; + msize = ref_size; + if (msize >= 0x10000) { + msize = 0x10000; + break; } } } @@ -235,13 +178,15 @@ void *diff_delta(void *from_buf, unsigned long from_size, inscnt = 0; } } else { + unsigned char *op; + if (inscnt) { out[outpos - inscnt - 1] = inscnt; inscnt = 0; } data += msize; - orig = out + outpos++; + op = out + outpos++; i = 0x80; if (moff & 0xff) { out[outpos++] = moff; i |= 0x01; } @@ -256,23 +201,21 @@ void *diff_delta(void *from_buf, unsigned long from_size, msize >>= 8; if (msize & 0xff) { out[outpos++] = msize; i |= 0x20; } - *orig = i; - } - - if (max_size && outpos > max_size) { - free(out); - delta_cleanup(&bdf); - return NULL; + *op = i; } - /* next time around the largest possible output is 1 + 4 + 3 */ - if (outpos > outsize - 8) { + if (outpos >= outsize - MAX_OP_SIZE) { void *tmp = out; outsize = outsize * 3 / 2; - out = realloc(out, outsize); + if (max_size && outsize >= max_size) + outsize = max_size + MAX_OP_SIZE + 1; + if (max_size && outpos > max_size) + out = NULL; + else + out = realloc(out, outsize); if (!out) { free(tmp); - delta_cleanup(&bdf); + free(hash); return NULL; } } @@ -281,7 +224,7 @@ void *diff_delta(void *from_buf, unsigned long from_size, if (inscnt) out[outpos - inscnt - 1] = inscnt; - delta_cleanup(&bdf); + free(hash); *delta_size = outpos; return out; } diff --git a/diffcore-break.c b/diffcore-break.c index 0fc2b860be..71ad58a25a 100644 --- a/diffcore-break.c +++ b/diffcore-break.c @@ -45,8 +45,8 @@ static int should_break(struct diff_filespec *src, * The value we return is 1 if we want the pair to be broken, * or 0 if we do not. */ - unsigned long delta_size, base_size, src_copied, literal_added; - int to_break = 0; + unsigned long delta_size, base_size, src_copied, literal_added, + src_removed; *merge_score_p = 0; /* assume no deletion --- "do not break" * is the default. @@ -72,33 +72,40 @@ static int should_break(struct diff_filespec *src, &src_copied, &literal_added)) return 0; + /* sanity */ + if (src->size < src_copied) + src_copied = src->size; + if (dst->size < literal_added + src_copied) { + if (src_copied < dst->size) + literal_added = dst->size - src_copied; + else + literal_added = 0; + } + src_removed = src->size - src_copied; + /* Compute merge-score, which is "how much is removed * from the source material". The clean-up stage will * merge the surviving pair together if the score is * less than the minimum, after rename/copy runs. */ - if (src->size <= src_copied) - ; /* all copied, nothing removed */ - else { - delta_size = src->size - src_copied; - *merge_score_p = delta_size * MAX_SCORE / src->size; - } - + *merge_score_p = src_removed * MAX_SCORE / src->size; + /* Extent of damage, which counts both inserts and * deletes. */ - if (src->size + literal_added <= src_copied) - delta_size = 0; /* avoid wrapping around */ - else - delta_size = (src->size - src_copied) + literal_added; - - /* We break if the edit exceeds the minimum. - * i.e. (break_score / MAX_SCORE < delta_size / base_size) + delta_size = src_removed + literal_added; + if (delta_size * MAX_SCORE / base_size < break_score) + return 0; + + /* If you removed a lot without adding new material, that is + * not really a rewrite. */ - if (break_score * base_size < delta_size * MAX_SCORE) - to_break = 1; + if ((src->size * break_score < src_removed * MAX_SCORE) && + (literal_added * 20 < src_removed) && + (literal_added * 20 < src_copied)) + return 0; - return to_break; + return 1; } void diffcore_break(int break_score) diff --git a/diffcore-delta.c b/diffcore-delta.c index d03787be65..70bacff837 100644 --- a/diffcore-delta.c +++ b/diffcore-delta.c @@ -2,87 +2,52 @@ #include "diff.h" #include "diffcore.h" -struct linehash { - unsigned long bytes; - unsigned long hash; -}; +/* + * Idea here is very simple. + * + * We have total of (sz-N+1) N-byte overlapping sequences in buf whose + * size is sz. If the same N-byte sequence appears in both source and + * destination, we say the byte that starts that sequence is shared + * between them (i.e. copied from source to destination). + * + * For each possible N-byte sequence, if the source buffer has more + * instances of it than the destination buffer, that means the + * difference are the number of bytes not copied from source to + * destination. If the counts are the same, everything was copied + * from source to destination. If the destination has more, + * everything was copied, and destination added more. + * + * We are doing an approximation so we do not really have to waste + * memory by actually storing the sequence. We just hash them into + * somewhere around 2^16 hashbuckets and count the occurrences. + * + * The length of the sequence is arbitrarily set to 8 for now. + */ -static unsigned long hash_extended_line(const unsigned char **buf_p, - unsigned long left) -{ - /* An extended line is zero or more whitespace letters (including LF) - * followed by one non whitespace letter followed by zero or more - * non LF, and terminated with by a LF (or EOF). - */ - const unsigned char *bol = *buf_p; - const unsigned char *buf = bol; - unsigned long hashval = 0; - while (left) { - unsigned c = *buf++; - if (!c) - goto binary; - left--; - if (' ' < c) { - hashval = c; - break; - } - } - while (left) { - unsigned c = *buf++; - if (!c) - goto binary; - left--; - if (c == '\n') - break; - if (' ' < c) - hashval = hashval * 11 + c; - } - *buf_p = buf; - return hashval; - - binary: - *buf_p = NULL; - return 0; -} - -static int linehash_compare(const void *a_, const void *b_) -{ - struct linehash *a = (struct linehash *) a_; - struct linehash *b = (struct linehash *) b_; - if (a->hash < b->hash) return -1; - if (a->hash > b->hash) return 1; - return 0; -} +#define HASHBASE 65537 /* next_prime(2^16) */ -static struct linehash *hash_lines(const unsigned char *buf, - unsigned long size) +static void hash_chars(unsigned char *buf, unsigned long sz, int *count) { - const unsigned char *eobuf = buf + size; - struct linehash *line = NULL; - int alloc = 0, used = 0; + unsigned int accum1, accum2, i; - while (buf < eobuf) { - const unsigned char *ptr = buf; - unsigned long hash = hash_extended_line(&buf, eobuf-ptr); - if (!buf) { - free(line); - return NULL; - } - if (alloc <= used) { - alloc = alloc_nr(alloc); - line = xrealloc(line, sizeof(*line) * alloc); - } - line[used].bytes = buf - ptr; - line[used].hash = hash; - used++; + /* an 8-byte shift register made of accum1 and accum2. New + * bytes come at LSB of accum2, and shifted up to accum1 + */ + for (i = accum1 = accum2 = 0; i < 7; i++, sz--) { + accum1 = (accum1 << 8) | (accum2 >> 24); + accum2 = (accum2 << 8) | *buf++; + } + while (sz) { + accum1 = (accum1 << 8) | (accum2 >> 24); + accum2 = (accum2 << 8) | *buf++; + /* We want something that hashes permuted byte + * sequences nicely; simpler hash like (accum1 ^ + * accum2) does not perform as well. + */ + i = (accum1 + accum2 * 0x61) % HASHBASE; + count[i]++; + sz--; } - qsort(line, used, sizeof(*line), linehash_compare); - - /* Terminate the list */ - if (alloc <= used) - line = xrealloc(line, sizeof(*line) * (used+1)); - line[used].bytes = line[used].hash = 0; - return line; } int diffcore_count_changes(void *src, unsigned long src_size, @@ -91,38 +56,28 @@ int diffcore_count_changes(void *src, unsigned long src_size, unsigned long *src_copied, unsigned long *literal_added) { - struct linehash *src_lines, *dst_lines; + int *src_count, *dst_count, i; unsigned long sc, la; - src_lines = hash_lines(src, src_size); - if (!src_lines) - return -1; - dst_lines = hash_lines(dst, dst_size); - if (!dst_lines) { - free(src_lines); + if (src_size < 8 || dst_size < 8) return -1; - } + + src_count = xcalloc(HASHBASE * 2, sizeof(int)); + dst_count = src_count + HASHBASE; + hash_chars(src, src_size, src_count); + hash_chars(dst, dst_size, dst_count); + sc = la = 0; - while (src_lines->bytes && dst_lines->bytes) { - int cmp = linehash_compare(src_lines, dst_lines); - if (!cmp) { - sc += src_lines->bytes; - src_lines++; - dst_lines++; - continue; + for (i = 0; i < HASHBASE; i++) { + if (src_count[i] < dst_count[i]) { + la += dst_count[i] - src_count[i]; + sc += src_count[i]; } - if (cmp < 0) { - src_lines++; - continue; - } - la += dst_lines->bytes; - dst_lines++; - } - while (dst_lines->bytes) { - la += dst_lines->bytes; - dst_lines++; + else /* i.e. if (dst_count[i] <= src_count[i]) */ + sc += dst_count[i]; } *src_copied = sc; *literal_added = la; + free(src_count); return 0; } diff --git a/diffcore.h b/diffcore.h index dba4f17658..d31b3b476c 100644 --- a/diffcore.h +++ b/diffcore.h @@ -17,8 +17,8 @@ */ #define MAX_SCORE 60000.0 #define DEFAULT_RENAME_SCORE 30000 /* rename/copy similarity minimum (50%) */ -#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%)*/ -#define DEFAULT_MERGE_SCORE 48000 /* maximum for break-merge to happen (80%)*/ +#define DEFAULT_BREAK_SCORE 30000 /* minimum for break to happen (50%) */ +#define DEFAULT_MERGE_SCORE 36000 /* maximum for break-merge to happen 60%) */ #define MINIMUM_BREAK_SIZE 400 /* do not break a file smaller than this */ diff --git a/environment.c b/environment.c index 251e53ca09..16c08f0697 100644 --- a/environment.c +++ b/environment.c @@ -17,6 +17,7 @@ int only_use_symrefs = 0; int repository_format_version = 0; char git_commit_encoding[MAX_ENCODING_LENGTH] = "utf-8"; int shared_repository = 0; +const char *apply_default_whitespace = NULL; static char *git_dir, *git_object_dir, *git_index_file, *git_refs_dir, *git_graft_file; diff --git a/epoch.c b/epoch.c deleted file mode 100644 index 3a767486da..0000000000 --- a/epoch.c +++ /dev/null @@ -1,639 +0,0 @@ -/* - * Copyright (c) 2005, Jon Seymour - * - * For more information about epoch theory on which this module is based, - * refer to http://blackcubes.dyndns.org/epoch/. That web page defines - * terms such as "epoch" and "minimal, non-linear epoch" and provides rationales - * for some of the algorithms used here. - * - */ -#include <stdlib.h> - -/* Provides arbitrary precision integers required to accurately represent - * fractional mass: */ -#include <openssl/bn.h> - -#include "cache.h" -#include "commit.h" -#include "epoch.h" - -struct fraction { - BIGNUM numerator; - BIGNUM denominator; -}; - -#define HAS_EXACTLY_ONE_PARENT(n) ((n)->parents && !(n)->parents->next) - -static BN_CTX *context = NULL; -static struct fraction *one = NULL; -static struct fraction *zero = NULL; - -static BN_CTX *get_BN_CTX(void) -{ - if (!context) { - context = BN_CTX_new(); - } - return context; -} - -static struct fraction *new_zero(void) -{ - struct fraction *result = xmalloc(sizeof(*result)); - BN_init(&result->numerator); - BN_init(&result->denominator); - BN_zero(&result->numerator); - BN_one(&result->denominator); - return result; -} - -static void clear_fraction(struct fraction *fraction) -{ - BN_clear(&fraction->numerator); - BN_clear(&fraction->denominator); -} - -static struct fraction *divide(struct fraction *result, struct fraction *fraction, int divisor) -{ - BIGNUM bn_divisor; - - BN_init(&bn_divisor); - BN_set_word(&bn_divisor, divisor); - - BN_copy(&result->numerator, &fraction->numerator); - BN_mul(&result->denominator, &fraction->denominator, &bn_divisor, get_BN_CTX()); - - BN_clear(&bn_divisor); - return result; -} - -static struct fraction *init_fraction(struct fraction *fraction) -{ - BN_init(&fraction->numerator); - BN_init(&fraction->denominator); - BN_zero(&fraction->numerator); - BN_one(&fraction->denominator); - return fraction; -} - -static struct fraction *get_one(void) -{ - if (!one) { - one = new_zero(); - BN_one(&one->numerator); - } - return one; -} - -static struct fraction *get_zero(void) -{ - if (!zero) { - zero = new_zero(); - } - return zero; -} - -static struct fraction *copy(struct fraction *to, struct fraction *from) -{ - BN_copy(&to->numerator, &from->numerator); - BN_copy(&to->denominator, &from->denominator); - return to; -} - -static struct fraction *add(struct fraction *result, struct fraction *left, struct fraction *right) -{ - BIGNUM a, b, gcd; - - BN_init(&a); - BN_init(&b); - BN_init(&gcd); - - BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX()); - BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX()); - BN_mul(&result->denominator, &left->denominator, &right->denominator, get_BN_CTX()); - BN_add(&result->numerator, &a, &b); - - BN_gcd(&gcd, &result->denominator, &result->numerator, get_BN_CTX()); - BN_div(&result->denominator, NULL, &result->denominator, &gcd, get_BN_CTX()); - BN_div(&result->numerator, NULL, &result->numerator, &gcd, get_BN_CTX()); - - BN_clear(&a); - BN_clear(&b); - BN_clear(&gcd); - - return result; -} - -static int compare(struct fraction *left, struct fraction *right) -{ - BIGNUM a, b; - int result; - - BN_init(&a); - BN_init(&b); - - BN_mul(&a, &left->numerator, &right->denominator, get_BN_CTX()); - BN_mul(&b, &left->denominator, &right->numerator, get_BN_CTX()); - - result = BN_cmp(&a, &b); - - BN_clear(&a); - BN_clear(&b); - - return result; -} - -struct mass_counter { - struct fraction seen; - struct fraction pending; -}; - -static struct mass_counter *new_mass_counter(struct commit *commit, struct fraction *pending) -{ - struct mass_counter *mass_counter = xmalloc(sizeof(*mass_counter)); - memset(mass_counter, 0, sizeof(*mass_counter)); - - init_fraction(&mass_counter->seen); - init_fraction(&mass_counter->pending); - - copy(&mass_counter->pending, pending); - copy(&mass_counter->seen, get_zero()); - - if (commit->object.util) { - die("multiple attempts to initialize mass counter for %s", - sha1_to_hex(commit->object.sha1)); - } - - commit->object.util = mass_counter; - - return mass_counter; -} - -static void free_mass_counter(struct mass_counter *counter) -{ - clear_fraction(&counter->seen); - clear_fraction(&counter->pending); - free(counter); -} - -/* - * Finds the base commit of a list of commits. - * - * One property of the commit being searched for is that every commit reachable - * from the base commit is reachable from the commits in the starting list only - * via paths that include the base commit. - * - * This algorithm uses a conservation of mass approach to find the base commit. - * - * We start by injecting one unit of mass into the graph at each - * of the commits in the starting list. Injecting mass into a commit - * is achieved by adding to its pending mass counter and, if it is not already - * enqueued, enqueuing the commit in a list of pending commits, in latest - * commit date first order. - * - * The algorithm then proceeds to visit each commit in the pending queue. - * Upon each visit, the pending mass is added to the mass already seen for that - * commit and then divided into N equal portions, where N is the number of - * parents of the commit being visited. The divided portions are then injected - * into each of the parents. - * - * The algorithm continues until we discover a commit which has seen all the - * mass originally injected or until we run out of things to do. - * - * If we find a commit that has seen all the original mass, we have found - * the common base of all the commits in the starting list. - * - * The algorithm does _not_ depend on accurate timestamps for correct operation. - * However, reasonably sane (e.g. non-random) timestamps are required in order - * to prevent an exponential performance characteristic. The occasional - * timestamp inaccuracy will not dramatically affect performance but may - * result in more nodes being processed than strictly necessary. - * - * This procedure sets *boundary to the address of the base commit. It returns - * non-zero if, and only if, there was a problem parsing one of the - * commits discovered during the traversal. - */ -static int find_base_for_list(struct commit_list *list, struct commit **boundary) -{ - int ret = 0; - struct commit_list *cleaner = NULL; - struct commit_list *pending = NULL; - struct fraction injected; - init_fraction(&injected); - *boundary = NULL; - - for (; list; list = list->next) { - struct commit *item = list->item; - - if (!item->object.util) { - new_mass_counter(list->item, get_one()); - add(&injected, &injected, get_one()); - - commit_list_insert(list->item, &cleaner); - commit_list_insert(list->item, &pending); - } - } - - while (!*boundary && pending && !ret) { - struct commit *latest = pop_commit(&pending); - struct mass_counter *latest_node = (struct mass_counter *) latest->object.util; - int num_parents; - - if ((ret = parse_commit(latest))) - continue; - add(&latest_node->seen, &latest_node->seen, &latest_node->pending); - - num_parents = count_parents(latest); - if (num_parents) { - struct fraction distribution; - struct commit_list *parents; - - divide(init_fraction(&distribution), &latest_node->pending, num_parents); - - for (parents = latest->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - struct mass_counter *parent_node = (struct mass_counter *) parent->object.util; - - if (!parent_node) { - parent_node = new_mass_counter(parent, &distribution); - insert_by_date(parent, &pending); - commit_list_insert(parent, &cleaner); - } else { - if (!compare(&parent_node->pending, get_zero())) - insert_by_date(parent, &pending); - add(&parent_node->pending, &parent_node->pending, &distribution); - } - } - - clear_fraction(&distribution); - } - - if (!compare(&latest_node->seen, &injected)) - *boundary = latest; - copy(&latest_node->pending, get_zero()); - } - - while (cleaner) { - struct commit *next = pop_commit(&cleaner); - free_mass_counter((struct mass_counter *) next->object.util); - next->object.util = NULL; - } - - if (pending) - free_commit_list(pending); - - clear_fraction(&injected); - return ret; -} - - -/* - * Finds the base of an minimal, non-linear epoch, headed at head, by - * applying the find_base_for_list to a list consisting of the parents - */ -static int find_base(struct commit *head, struct commit **boundary) -{ - int ret = 0; - struct commit_list *pending = NULL; - struct commit_list *next; - - for (next = head->parents; next; next = next->next) { - commit_list_insert(next->item, &pending); - } - ret = find_base_for_list(pending, boundary); - free_commit_list(pending); - - return ret; -} - -/* - * This procedure traverses to the boundary of the first epoch in the epoch - * sequence of the epoch headed at head_of_epoch. This is either the end of - * the maximal linear epoch or the base of a minimal non-linear epoch. - * - * The queue of pending nodes is sorted in reverse date order and each node - * is currently in the queue at most once. - */ -static int find_next_epoch_boundary(struct commit *head_of_epoch, struct commit **boundary) -{ - int ret; - struct commit *item = head_of_epoch; - - ret = parse_commit(item); - if (ret) - return ret; - - if (HAS_EXACTLY_ONE_PARENT(item)) { - /* - * We are at the start of a maximimal linear epoch. - * Traverse to the end. - */ - while (HAS_EXACTLY_ONE_PARENT(item) && !ret) { - item = item->parents->item; - ret = parse_commit(item); - } - *boundary = item; - - } else { - /* - * Otherwise, we are at the start of a minimal, non-linear - * epoch - find the common base of all parents. - */ - ret = find_base(item, boundary); - } - - return ret; -} - -/* - * Returns non-zero if parent is known to be a parent of child. - */ -static int is_parent_of(struct commit *parent, struct commit *child) -{ - struct commit_list *parents; - for (parents = child->parents; parents; parents = parents->next) { - if (!memcmp(parent->object.sha1, parents->item->object.sha1, - sizeof(parents->item->object.sha1))) - return 1; - } - return 0; -} - -/* - * Pushes an item onto the merge order stack. If the top of the stack is - * marked as being a possible "break", we check to see whether it actually - * is a break. - */ -static void push_onto_merge_order_stack(struct commit_list **stack, struct commit *item) -{ - struct commit_list *top = *stack; - if (top && (top->item->object.flags & DISCONTINUITY)) { - if (is_parent_of(top->item, item)) { - top->item->object.flags &= ~DISCONTINUITY; - } - } - commit_list_insert(item, stack); -} - -/* - * Marks all interesting, visited commits reachable from this commit - * as uninteresting. We stop recursing when we reach the epoch boundary, - * an unvisited node or a node that has already been marking uninteresting. - * - * This doesn't actually mark all ancestors between the start node and the - * epoch boundary uninteresting, but does ensure that they will eventually - * be marked uninteresting when the main sort_first_epoch() traversal - * eventually reaches them. - */ -static void mark_ancestors_uninteresting(struct commit *commit) -{ - unsigned int flags = commit->object.flags; - int visited = flags & VISITED; - int boundary = flags & BOUNDARY; - int uninteresting = flags & UNINTERESTING; - struct commit_list *next; - - commit->object.flags |= UNINTERESTING; - - /* - * We only need to recurse if - * we are not on the boundary and - * we have not already been marked uninteresting and - * we have already been visited. - * - * The main sort_first_epoch traverse will mark unreachable - * all uninteresting, unvisited parents as they are visited - * so there is no need to duplicate that traversal here. - * - * Similarly, if we are already marked uninteresting - * then either all ancestors have already been marked - * uninteresting or will be once the sort_first_epoch - * traverse reaches them. - */ - - if (uninteresting || boundary || !visited) - return; - - for (next = commit->parents; next; next = next->next) - mark_ancestors_uninteresting(next->item); -} - -/* - * Sorts the nodes of the first epoch of the epoch sequence of the epoch headed at head - * into merge order. - */ -static void sort_first_epoch(struct commit *head, struct commit_list **stack) -{ - struct commit_list *parents; - - head->object.flags |= VISITED; - - /* - * TODO: By sorting the parents in a different order, we can alter the - * merge order to show contemporaneous changes in parallel branches - * occurring after "local" changes. This is useful for a developer - * when a developer wants to see all changes that were incorporated - * into the same merge as her own changes occur after her own - * changes. - */ - - for (parents = head->parents; parents; parents = parents->next) { - struct commit *parent = parents->item; - - if (head->object.flags & UNINTERESTING) { - /* - * Propagates the uninteresting bit to all parents. - * if we have already visited this parent, then - * the uninteresting bit will be propagated to each - * reachable commit that is still not marked - * uninteresting and won't otherwise be reached. - */ - mark_ancestors_uninteresting(parent); - } - - if (!(parent->object.flags & VISITED)) { - if (parent->object.flags & BOUNDARY) { - if (*stack) { - die("something else is on the stack - %s", - sha1_to_hex((*stack)->item->object.sha1)); - } - push_onto_merge_order_stack(stack, parent); - parent->object.flags |= VISITED; - - } else { - sort_first_epoch(parent, stack); - if (parents) { - /* - * This indicates a possible - * discontinuity it may not be be - * actual discontinuity if the head - * of parent N happens to be the tail - * of parent N+1. - * - * The next push onto the stack will - * resolve the question. - */ - (*stack)->item->object.flags |= DISCONTINUITY; - } - } - } - } - - push_onto_merge_order_stack(stack, head); -} - -/* - * Emit the contents of the stack. - * - * The stack is freed and replaced by NULL. - * - * Sets the return value to STOP if no further output should be generated. - */ -static int emit_stack(struct commit_list **stack, emitter_func emitter, int include_last) -{ - unsigned int seen = 0; - int action = CONTINUE; - - while (*stack && (action != STOP)) { - struct commit *next = pop_commit(stack); - seen |= next->object.flags; - if (*stack || include_last) { - if (!*stack) - next->object.flags |= BOUNDARY; - action = emitter(next); - } - } - - if (*stack) { - free_commit_list(*stack); - *stack = NULL; - } - - return (action == STOP || (seen & UNINTERESTING)) ? STOP : CONTINUE; -} - -/* - * Sorts an arbitrary epoch into merge order by sorting each epoch - * of its epoch sequence into order. - * - * Note: this algorithm currently leaves traces of its execution in the - * object flags of nodes it discovers. This should probably be fixed. - */ -static int sort_in_merge_order(struct commit *head_of_epoch, emitter_func emitter) -{ - struct commit *next = head_of_epoch; - int ret = 0; - int action = CONTINUE; - - ret = parse_commit(head_of_epoch); - - next->object.flags |= BOUNDARY; - - while (next && next->parents && !ret && (action != STOP)) { - struct commit *base = NULL; - - ret = find_next_epoch_boundary(next, &base); - if (ret) - return ret; - next->object.flags |= BOUNDARY; - if (base) - base->object.flags |= BOUNDARY; - - if (HAS_EXACTLY_ONE_PARENT(next)) { - while (HAS_EXACTLY_ONE_PARENT(next) - && (action != STOP) - && !ret) { - if (next->object.flags & UNINTERESTING) { - action = STOP; - } else { - action = emitter(next); - } - if (action != STOP) { - next = next->parents->item; - ret = parse_commit(next); - } - } - - } else { - struct commit_list *stack = NULL; - sort_first_epoch(next, &stack); - action = emit_stack(&stack, emitter, (base == NULL)); - next = base; - } - } - - if (next && (action != STOP) && !ret) { - emitter(next); - } - - return ret; -} - -/* - * Sorts the nodes reachable from a starting list in merge order, we - * first find the base for the starting list and then sort all nodes - * in this subgraph using the sort_first_epoch algorithm. Once we have - * reached the base we can continue sorting using sort_in_merge_order. - */ -int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter) -{ - struct commit_list *stack = NULL; - struct commit *base; - int ret = 0; - int action = CONTINUE; - struct commit_list *reversed = NULL; - - for (; list; list = list->next) - commit_list_insert(list->item, &reversed); - - if (!reversed) - return ret; - else if (!reversed->next) { - /* - * If there is only one element in the list, we can sort it - * using sort_in_merge_order. - */ - base = reversed->item; - } else { - /* - * Otherwise, we search for the base of the list. - */ - ret = find_base_for_list(reversed, &base); - if (ret) - return ret; - if (base) - base->object.flags |= BOUNDARY; - - while (reversed) { - struct commit * next = pop_commit(&reversed); - - if (!(next->object.flags & VISITED) && next!=base) { - sort_first_epoch(next, &stack); - if (reversed) { - /* - * If we have more commits - * to push, then the first - * push for the next parent may - * (or may * not) represent a - * discontinuity with respect - * to the parent currently on - * the top of the stack. - * - * Mark it for checking here, - * and check it with the next - * push. See sort_first_epoch() - * for more details. - */ - stack->item->object.flags |= DISCONTINUITY; - } - } - } - - action = emit_stack(&stack, emitter, (base==NULL)); - } - - if (base && (action != STOP)) { - ret = sort_in_merge_order(base, emitter); - } - - return ret; -} diff --git a/epoch.h b/epoch.h deleted file mode 100644 index 7493d5a241..0000000000 --- a/epoch.h +++ /dev/null @@ -1,21 +0,0 @@ -#ifndef EPOCH_H -#define EPOCH_H - - -// return codes for emitter_func -#define STOP 0 -#define CONTINUE 1 -#define DO 2 -typedef int (*emitter_func) (struct commit *); - -int sort_list_in_merge_order(struct commit_list *list, emitter_func emitter); - -/* Low bits are used by rev-list */ -#define UNINTERESTING (1u<<10) -#define BOUNDARY (1u<<11) -#define VISITED (1u<<12) -#define DISCONTINUITY (1u<<13) -#define LAST_EPOCH_FLAG (1u<<14) - - -#endif /* EPOCH_H */ @@ -2,7 +2,8 @@ # # Copyright (c) 2005, 2006 Junio C Hamano -USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] <mbox> +USAGE='[--signoff] [--dotest=<dir>] [--utf8] [--binary] [--3way] + [--interactive] [--whitespace=<option>] <mbox>... or, when resuming [--skip | --resolved]' . git-sh-setup @@ -100,7 +101,7 @@ fall_back_3way () { } prec=4 -dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= +dotest=.dotest sign= utf8= keep= skip= interactive= resolved= binary= ws= while case "$#" in 0) break;; esac do @@ -133,6 +134,9 @@ do --sk|--ski|--skip) skip=t; shift ;; + --whitespace=*) + ws=$1; shift ;; + --) shift; break ;; -*) @@ -171,10 +175,11 @@ else exit 1 } - # -b, -s, -u and -k flags are kept for the resuming session after - # a patch failure. + # -b, -s, -u, -k and --whitespace flags are kept for the + # resuming session after a patch failure. # -3 and -i can and must be given when resuming. echo "$binary" >"$dotest/binary" + echo " $ws" >"$dotest/whitespace" echo "$sign" >"$dotest/sign" echo "$utf8" >"$dotest/utf8" echo "$keep" >"$dotest/keep" @@ -202,6 +207,7 @@ if test "$(cat "$dotest/keep")" = t then keep=-k fi +ws=`cat "$dotest/whitespace"` if test "$(cat "$dotest/sign")" = t then SIGNOFF=`git-var GIT_COMMITTER_IDENT | sed -e ' @@ -355,7 +361,7 @@ do case "$resolved" in '') - git-apply $binary --index "$dotest/patch" + git-apply $binary --index $ws "$dotest/patch" apply_status=$? ;; t) diff --git a/git-annotate.perl b/git-annotate.perl index f9c2c6caf5..d93ee19c7e 100755 --- a/git-annotate.perl +++ b/git-annotate.perl @@ -15,6 +15,8 @@ sub usage() { print STDERR 'Usage: ${\basename $0} [-s] [-S revs-file] file [ revision ] -l, --long Show long rev (Defaults off) + -t, --time + Show raw timestamp (Defaults off) -r, --rename Follow renames (Defaults on). -S, --rev-file revs-file @@ -26,12 +28,13 @@ sub usage() { exit(1); } -our ($help, $longrev, $rename, $starting_rev, $rev_file) = (0, 0, 1); +our ($help, $longrev, $rename, $rawtime, $starting_rev, $rev_file) = (0, 0, 1); my $rc = GetOptions( "long|l" => \$longrev, + "time|t" => \$rawtime, "help|h" => \$help, "rename|r" => \$rename, - "rev-file|S" => \$rev_file); + "rev-file|S=s" => \$rev_file); if (!$rc or $help) { usage(); } @@ -125,7 +128,7 @@ foreach my $l (@filelines) { } printf("%s\t(%10s\t%10s\t%d)%s\n", $rev, $committer, - format_date($date), $i++, $output); + format_date($date), ++$i, $output); } sub init_claim { @@ -174,7 +177,8 @@ sub git_rev_list { my $revlist; if ($rev_file) { - open($revlist, '<' . $rev_file); + open($revlist, '<' . $rev_file) + or die "Failed to open $rev_file : $!"; } else { $revlist = open_pipe("git-rev-list","--parents","--remove-empty",$rev,"--",$file) or die "Failed to exec git-rev-list: $!"; @@ -304,6 +308,12 @@ sub _git_diff_parse { } $ri++; + } elsif (m/^\\/) { + ; + # Skip \No newline at end of file. + # But this can be internationalized, so only look + # for an initial \ + } else { if (substr($_,1) ne get_line($slines,$ri) ) { die sprintf("Line %d (%d) does not match:\n|%s\n|%s\n%s => %s\n", @@ -404,8 +414,10 @@ sub git_commit_info { } sub format_date { + if ($rawtime) { + return $_[0]; + } my ($timestamp, $timezone) = split(' ', $_[0]); - return strftime("%Y-%m-%d %H:%M:%S " . $timezone, gmtime($timestamp)); } diff --git a/git-archimport.perl b/git-archimport.perl index 6792624d46..740bc1fd52 100755 --- a/git-archimport.perl +++ b/git-archimport.perl @@ -928,7 +928,7 @@ sub find_parents { # now walk up to the mergepoint collecting what patches we have my $branchtip = git_rev_parse($ps->{branch}); - my @ancestors = `git-rev-list --merge-order $branchtip ^$mergebase`; + my @ancestors = `git-rev-list --topo-order $branchtip ^$mergebase`; my %have; # collected merges this branch has foreach my $merge (@{$ps->{merges}}) { $have{$merge} = 1; @@ -951,7 +951,7 @@ sub find_parents { # see what the remote branch has - these are the merges we # will want to have in a consecutive series from the mergebase my $otherbranchtip = git_rev_parse($branch); - my @needraw = `git-rev-list --merge-order $otherbranchtip ^$mergebase`; + my @needraw = `git-rev-list --topo-order $otherbranchtip ^$mergebase`; my @need; foreach my $needps (@needraw) { # get the psets $needps = commitid2pset($needps); diff --git a/git-branch.sh b/git-branch.sh index 6ac961e6d1..663a3a370c 100755 --- a/git-branch.sh +++ b/git-branch.sh @@ -48,6 +48,12 @@ If you are sure you want to delete it, run 'git branch -D $branch_name'." exit 0 } +ls_remote_branches () { + git-rev-parse --symbolic --all | + sed -ne 's|^refs/\(remotes/\)|\1|p' | + sort +} + force= while case "$#,$1" in 0,*) break ;; *,-*) ;; *) break ;; esac do @@ -56,6 +62,10 @@ do delete_branch "$@" exit ;; + -r) + ls_remote_branches + exit + ;; -f) force="$1" ;; diff --git a/git-commit.sh b/git-commit.sh index f7ee1aadee..d9ec1f14d9 100755 --- a/git-commit.sh +++ b/git-commit.sh @@ -3,7 +3,7 @@ # Copyright (c) 2005 Linus Torvalds # Copyright (c) 2006 Junio C Hamano -USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>] [-e] [--author <author>] [[-i | -o] <path>...]' +USAGE='[-a] [-s] [-v] [--no-verify] [-m <message> | -F <logfile> | (-C|-c) <commit>) [--amend] [-e] [--author <author>] [[-i | -o] <path>...]' SUBDIRECTORY_OK=Yes . git-sh-setup @@ -64,6 +64,22 @@ run_status () { # We always show status for the whole tree. cd "$TOP" + IS_INITIAL="$initial_commit" + REFERENCE=HEAD + case "$amend" in + t) + # If we are amending the initial commit, there + # is no HEAD^1. + if git-rev-parse --verify "HEAD^1" >/dev/null 2>&1 + then + REFERENCE="HEAD^1" + IS_INITIAL= + else + IS_INITIAL=t + fi + ;; + esac + # If TMP_INDEX is defined, that means we are doing # "--only" partial commit, and that index file is used # to build the tree for the commit. Otherwise, if @@ -85,10 +101,10 @@ run_status () { *) echo "# On branch $branch" ;; esac - if test -z "$initial_commit" + if test -z "$IS_INITIAL" then git-diff-index -M --cached --name-status \ - --diff-filter=MDTCRA HEAD | + --diff-filter=MDTCRA $REFERENCE | sed -e ' s/\\/\\\\/g s/ /\\ /g @@ -147,7 +163,7 @@ run_status () { if test -n "$verbose" then - git-diff-index --cached -M -p --diff-filter=MDTCRA HEAD + git-diff-index --cached -M -p --diff-filter=MDTCRA $REFERENCE fi case "$committable" in 0) @@ -173,6 +189,7 @@ also= only= logfile= use_commit= +amend= no_edit= log_given= log_message= @@ -254,6 +271,12 @@ do verify= shift ;; + --a|--am|--ame|--amen|--amend) + amend=t + log_given=t$log_given + use_commit=HEAD + shift + ;; -c) case "$#" in 1) usage ;; esac shift @@ -328,6 +351,15 @@ done ################################################################ # Sanity check options +case "$amend,$initial_commit" in +t,t) + die "You do not have anything to amend." ;; +t,) + if [ -f "$GIT_DIR/MERGE_HEAD" ]; then + die "You are in the middle of a merge -- cannot amend." + fi ;; +esac + case "$log_given" in tt*) die "Only one of -c/-C/-F/-m can be used." ;; @@ -559,13 +591,18 @@ if test -z "$initial_commit" then if [ -f "$GIT_DIR/MERGE_HEAD" ]; then PARENTS="-p HEAD "`sed -e 's/^/-p /' "$GIT_DIR/MERGE_HEAD"` + elif test -n "$amend"; then + PARENTS=$(git-cat-file commit HEAD | + sed -n -e '/^$/q' -e 's/^parent /-p /p') fi + current=$(git-rev-parse --verify HEAD) else if [ -z "$(git-ls-files)" ]; then echo >&2 Nothing to commit exit 1 fi PARENTS="" + current= fi { diff --git a/git-cvsserver.perl b/git-cvsserver.perl index d20d1a8c4b..7d3f78e375 100755 --- a/git-cvsserver.perl +++ b/git-cvsserver.perl @@ -53,6 +53,7 @@ my $methods = { 'Entry' => \&req_Entry, 'Modified' => \&req_Modified, 'Unchanged' => \&req_Unchanged, + 'Questionable' => \&req_Questionable, 'Argument' => \&req_Argument, 'Argumentx' => \&req_Argument, 'expand-modules' => \&req_expandmodules, @@ -63,6 +64,7 @@ my $methods = { 'ci' => \&req_ci, 'diff' => \&req_diff, 'log' => \&req_log, + 'rlog' => \&req_log, 'tag' => \&req_CATCHALL, 'status' => \&req_status, 'admin' => \&req_CATCHALL, @@ -85,6 +87,31 @@ $log->info("--------------- STARTING -----------------"); my $TEMP_DIR = tempdir( CLEANUP => 1 ); $log->debug("Temporary directory is '$TEMP_DIR'"); +# if we are called with a pserver argument, +# deal with the authentication cat before entereing the +# main loop +if (@ARGV && $ARGV[0] eq 'pserver') { + my $line = <STDIN>; chomp $line; + unless( $line eq 'BEGIN AUTH REQUEST') { + die "E Do not understand $line - expecting BEGIN AUTH REQUEST\n"; + } + $line = <STDIN>; chomp $line; + req_Root('root', $line) # reuse Root + or die "E Invalid root $line \n"; + $line = <STDIN>; chomp $line; + unless ($line eq 'anonymous') { + print "E Only anonymous user allowed via pserver\n"; + print "I HATE YOU\n"; + } + $line = <STDIN>; chomp $line; # validate the password? + $line = <STDIN>; chomp $line; + unless ($line eq 'END AUTH REQUEST') { + die "E Do not understand $line -- expecting END AUTH REQUEST\n"; + } + print "I LOVE YOU\n"; + # and now back to our regular programme... +} + # Keep going until the client closes the connection while (<STDIN>) { @@ -137,8 +164,21 @@ sub req_Root $state->{CVSROOT} = $data; $ENV{GIT_DIR} = $state->{CVSROOT} . "/"; + unless (-d $ENV{GIT_DIR} && -e $ENV{GIT_DIR}.'HEAD') { + print "E $ENV{GIT_DIR} does not seem to be a valid GIT repository\n"; + print "E \n"; + print "error 1 $ENV{GIT_DIR} is not a valid repository\n"; + return 0; + } - foreach my $line ( `git-var -l` ) + my @gitvars = `git-var -l`; + if ($?) { + print "E problems executing git-var on the server -- this is not a git repository or the PATH is not set correcly.\n"; + print "E \n"; + print "error 1 - problem executing git-var\n"; + return 0; + } + foreach my $line ( @gitvars ) { next unless ( $line =~ /^(.*?)\.(.*?)=(.*)$/ ); $cfg->{$1}{$2} = $3; @@ -150,6 +190,7 @@ sub req_Root print "E the repo config file needs a [gitcvs] section added, and the parameter 'enabled' set to 1\n"; print "E \n"; print "error 1 GITCVS emulation disabled\n"; + return 0; } if ( defined ( $cfg->{gitcvs}{logfile} ) ) @@ -158,6 +199,8 @@ sub req_Root } else { $log->nofile(); } + + return 1; } # Global_option option \n @@ -459,6 +502,22 @@ sub req_Unchanged #$log->debug("req_Unchanged : $data"); } +# Questionable filename \n +# Response expected: no. Additional data: no. +# Tell the server to check whether filename should be ignored, +# and if not, next time the server sends responses, send (in +# a M response) `?' followed by the directory and filename. +# filename must not contain `/'; it needs to be a file in the +# directory named by the most recent Directory request. +sub req_Questionable +{ + my ( $cmd, $data ) = @_; + + $state->{entries}{$state->{directory}.$data}{questionable} = 1; + + #$log->debug("req_Questionable : $data"); +} + # Argument text \n # Response expected: no. Save argument for use in a subsequent command. # Arguments accumulate until an argument-using command is given, at which @@ -553,8 +612,62 @@ sub req_co my $updater = GITCVS::updater->new($state->{CVSROOT}, $module, $log); $updater->update(); + $checkout_path =~ s|/$||; # get rid of trailing slashes + + # Eclipse seems to need the Clear-sticky command + # to prepare the 'Entries' file for the new directory. + print "Clear-sticky $checkout_path/\n"; + print $state->{CVSROOT} . "/$module/\n"; + print "Clear-static-directory $checkout_path/\n"; + print $state->{CVSROOT} . "/$module/\n"; + print "Clear-sticky $checkout_path/\n"; # yes, twice + print $state->{CVSROOT} . "/$module/\n"; + print "Template $checkout_path/\n"; + print $state->{CVSROOT} . "/$module/\n"; + print "0\n"; + # instruct the client that we're checking out to $checkout_path - print "E cvs server: updating $checkout_path\n"; + print "E cvs checkout: Updating $checkout_path\n"; + + my %seendirs = (); + my $lastdir =''; + + # recursive + sub prepdir { + my ($dir, $repodir, $remotedir, $seendirs) = @_; + my $parent = dirname($dir); + $dir =~ s|/+$||; + $repodir =~ s|/+$||; + $remotedir =~ s|/+$||; + $parent =~ s|/+$||; + $log->debug("announcedir $dir, $repodir, $remotedir" ); + + if ($parent eq '.' || $parent eq './') { + $parent = ''; + } + # recurse to announce unseen parents first + if (length($parent) && !exists($seendirs->{$parent})) { + prepdir($parent, $repodir, $remotedir, $seendirs); + } + # Announce that we are going to modify at the parent level + if ($parent) { + print "E cvs checkout: Updating $remotedir/$parent\n"; + } else { + print "E cvs checkout: Updating $remotedir\n"; + } + print "Clear-sticky $remotedir/$parent/\n"; + print "$repodir/$parent/\n"; + + print "Clear-static-directory $remotedir/$dir/\n"; + print "$repodir/$dir/\n"; + print "Clear-sticky $remotedir/$parent/\n"; # yes, twice + print "$repodir/$parent/\n"; + print "Template $remotedir/$dir/\n"; + print "$repodir/$dir/\n"; + print "0\n"; + + $seendirs->{$dir} = 1; + } foreach my $git ( @{$updater->gethead} ) { @@ -563,25 +676,32 @@ sub req_co ( $git->{name}, $git->{dir} ) = filenamesplit($git->{name}); + if (length($git->{dir}) && $git->{dir} ne './' + && $git->{dir} ne $lastdir ) { + unless (exists($seendirs{$git->{dir}})) { + prepdir($git->{dir}, $state->{CVSROOT} . "/$module/", + $checkout_path, \%seendirs); + $lastdir = $git->{dir}; + $seendirs{$git->{dir}} = 1; + } + print "E cvs checkout: Updating /$checkout_path/$git->{dir}\n"; + } + # modification time of this file print "Mod-time $git->{modified}\n"; # print some information to the client - print "MT +updated\n"; - print "MT text U\n"; if ( defined ( $git->{dir} ) and $git->{dir} ne "./" ) { - print "MT fname $checkout_path/$git->{dir}$git->{name}\n"; + print "M U $checkout_path/$git->{dir}$git->{name}\n"; } else { - print "MT fname $checkout_path/$git->{name}\n"; + print "M U $checkout_path/$git->{name}\n"; } - print "MT newline\n"; - print "MT -updated\n"; - # instruct client we're sending a file to put in this path - print "Created $checkout_path/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "\n"; + # instruct client we're sending a file to put in this path + print "Created $checkout_path/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "\n"; - print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; + print $state->{CVSROOT} . "/$module/" . ( defined ( $git->{dir} ) and $git->{dir} ne "./" ? $git->{dir} . "/" : "" ) . "$git->{name}\n"; # this is an "entries" line print "/$git->{name}/1.$git->{revision}///\n"; @@ -612,6 +732,26 @@ sub req_update argsplit("update"); + # + # It may just be a client exploring the available heads/modukles + # in that case, list them as top level directories and leave it + # at that. Eclipse uses this technique to offer you a list of + # projects (heads in this case) to checkout. + # + if ($state->{module} eq '') { + print "E cvs update: Updating .\n"; + opendir HEADS, $state->{CVSROOT} . '/refs/heads'; + while (my $head = readdir(HEADS)) { + if (-f $state->{CVSROOT} . '/refs/heads/' . $head) { + print "E cvs update: New directory `$head'\n"; + } + } + closedir HEADS; + print "ok\n"; + return 1; + } + + # Grab a handle to the SQLite db and do any necessary updates my $updater = GITCVS::updater->new($state->{CVSROOT}, $state->{module}, $log); @@ -657,8 +797,27 @@ sub req_update #$log->debug("Target revision is $meta->{revision}, current working revision is $wrev"); - # Files are up to date if the working copy and repo copy have the same revision, and the working copy is unmodified _and_ the user hasn't specified -C - next if ( defined ( $wrev ) and defined($meta->{revision}) and $wrev == $meta->{revision} and $state->{entries}{$filename}{unchanged} and not exists ( $state->{opt}{C} ) ); + # Files are up to date if the working copy and repo copy have the same revision, + # and the working copy is unmodified _and_ the user hasn't specified -C + next if ( defined ( $wrev ) + and defined($meta->{revision}) + and $wrev == $meta->{revision} + and $state->{entries}{$filename}{unchanged} + and not exists ( $state->{opt}{C} ) ); + + # If the working copy and repo copy have the same revision, + # but the working copy is modified, tell the client it's modified + if ( defined ( $wrev ) + and defined($meta->{revision}) + and $wrev == $meta->{revision} + and not exists ( $state->{opt}{C} ) ) + { + $log->info("Tell the client the file is modified"); + print "MT text U\n"; + print "MT fname $filename\n"; + print "MT newline\n"; + next; + } if ( $meta->{filehash} eq "deleted" ) { @@ -670,7 +829,8 @@ sub req_update print "Removed $dirpart\n"; print "$filepart\n"; } - elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) + elsif ( not defined ( $state->{entries}{$filename}{modified_hash} ) + or $state->{entries}{$filename}{modified_hash} eq $oldmeta->{filehash} ) { $log->info("Updating '$filename'"); # normal update, just send the new revision (either U=Update, or A=Add, or R=Remove) @@ -706,6 +866,7 @@ sub req_update # transmit file transmitfile($meta->{filehash}); } else { + $log->info("Updating '$filename'"); my ( $filepart, $dirpart ) = filenamesplit($meta->{name}); my $dir = tempdir( DIR => $TEMP_DIR, CLEANUP => 1 ) . "/"; @@ -781,6 +942,12 @@ sub req_ci $log->info("req_ci : " . ( defined($data) ? $data : "[NULL]" )); + if ( @ARGV && $ARGV[0] eq 'pserver') + { + print "error 1 pserver access cannot commit\n"; + exit; + } + if ( -e $state->{CVSROOT} . "/index" ) { print "error 1 Index already exists in git repo\n"; @@ -2271,7 +2438,7 @@ sub gethead return $self->{gethead_cache} if ( defined ( $self->{gethead_cache} ) ); - my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head",{},1); + my $db_query = $self->{dbh}->prepare_cached("SELECT name, filehash, mode, revision, modified, commithash, author FROM head ORDER BY name ASC",{},1); $db_query->execute(); my $tree = []; diff --git a/git-mv.perl b/git-mv.perl index 2ea852c918..75aa8feeb6 100755 --- a/git-mv.perl +++ b/git-mv.perl @@ -19,25 +19,26 @@ EOT exit(1); } -my $GIT_DIR = `git rev-parse --git-dir`; -exit 1 if $?; # rev-parse would have given "not a git dir" message. -chomp($GIT_DIR); - our ($opt_n, $opt_f, $opt_h, $opt_k, $opt_v); getopts("hnfkv") || usage; usage() if $opt_h; @ARGV >= 1 or usage; +my $GIT_DIR = `git rev-parse --git-dir`; +exit 1 if $?; # rev-parse would have given "not a git dir" message. +chomp($GIT_DIR); + my (@srcArgs, @dstArgs, @srcs, @dsts); my ($src, $dst, $base, $dstDir); +# remove any trailing slash in arguments +for (@ARGV) { s/\/*$//; } + my $argCount = scalar @ARGV; if (-d $ARGV[$argCount-1]) { $dstDir = $ARGV[$argCount-1]; - # remove any trailing slash - $dstDir =~ s/\/$//; @srcArgs = @ARGV[0..$argCount-2]; - + foreach $src (@srcArgs) { $base = $src; $base =~ s/^.*\///; @@ -46,10 +47,14 @@ if (-d $ARGV[$argCount-1]) { } } else { - if ($argCount != 2) { + if ($argCount < 2) { + print "Error: need at least two arguments\n"; + exit(1); + } + if ($argCount > 2) { print "Error: moving to directory '" . $ARGV[$argCount-1] - . "' not possible; not exisiting\n"; + . "' not possible; not existing\n"; exit(1); } @srcArgs = ($ARGV[0]); @@ -57,6 +62,24 @@ else { $dstDir = ""; } +my $subdir_prefix = `git rev-parse --show-prefix`; +chomp($subdir_prefix); + +# run in git base directory, so that git-ls-files lists all revisioned files +chdir "$GIT_DIR/.."; + +# normalize paths, needed to compare against versioned files and update-index +# also, this is nicer to end-users by doing ".//a/./b/.//./c" ==> "a/b/c" +for (@srcArgs, @dstArgs) { + # prepend git prefix as we run from base directory + $_ = $subdir_prefix.$_; + s|^\./||; + s|/\./|/| while (m|/\./|); + s|//+|/|g; + # Also "a/b/../c" ==> "a/c" + 1 while (s,(^|/)[^/]+/\.\./,$1,); +} + my (@allfiles,@srcfiles,@dstfiles); my $safesrc; my (%overwritten, %srcForDst); diff --git a/git-send-email.perl b/git-send-email.perl index b0d095b4e9..7c8d51223f 100755 --- a/git-send-email.perl +++ b/git-send-email.perl @@ -54,7 +54,7 @@ my $rc = GetOptions("from=s" => \$from, "compose" => \$compose, "quiet" => \$quiet, "suppress-from" => \$suppress_from, - "no-signed-off-cc" => \$no_signed_off_cc, + "no-signed-off-cc|no-signed-off-by-cc" => \$no_signed_off_cc, ); # Now, let's fill any that aren't set in with defaults: diff --git a/git-svnimport.perl b/git-svnimport.perl index 86837edbdd..639aa41861 100755 --- a/git-svnimport.perl +++ b/git-svnimport.perl @@ -13,6 +13,7 @@ use strict; use warnings; use Getopt::Std; +use File::Copy; use File::Spec; use File::Temp qw(tempfile); use File::Path qw(mkpath); @@ -68,10 +69,16 @@ if ($opt_M) { push (@mergerx, qr/$opt_M/); } +# Absolutize filename now, since we will have chdir'ed by the time we +# get around to opening it. +$opt_A = File::Spec->rel2abs($opt_A) if $opt_A; + our %users = (); -if ($opt_A) { - die "Cannot open $opt_A\n" unless -f $opt_A; - open(my $authors,$opt_A); +our $users_file = undef; +sub read_users($) { + $users_file = File::Spec->rel2abs(@_); + die "Cannot open $users_file\n" unless -f $users_file; + open(my $authors,$users_file); while(<$authors>) { chomp; next unless /^(\S+?)\s*=\s*(.+?)\s*<(.+)>\s*$/; @@ -302,6 +309,14 @@ EOM -d $git_dir or die "Could not create git subdir ($git_dir).\n"; +my $default_authors = "$git_dir/svn-authors"; +if ($opt_A) { + read_users($opt_A); + copy($opt_A,$default_authors) or die "Copy failed: $!"; +} else { + read_users($default_authors) if -f $default_authors; +} + open BRANCHES,">>", "$git_dir/svn2git"; sub node_kind($$$) { @@ -498,8 +513,8 @@ sub commit { if (not defined $author) { $author_name = $author_email = "unknown"; - } elsif ($opt_A) { - die "User $author is not listed in $opt_A\n" + } elsif (defined $users_file) { + die "User $author is not listed in $users_file\n" unless exists $users{$author}; ($author_name,$author_email) = @{$users{$author}}; } elsif ($author =~ /^(.*?)\s+<(.*)>$/) { diff --git a/git-verify-tag.sh b/git-verify-tag.sh index 726b1e706b..36f171b302 100755 --- a/git-verify-tag.sh +++ b/git-verify-tag.sh @@ -4,9 +4,21 @@ USAGE='<tag>' SUBDIRECTORY_OK='Yes' . git-sh-setup +verbose= +while case $# in 0) break;; esac +do + case "$1" in + -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) + verbose=t ;; + *) + break ;; + esac + shift +done + if [ "$#" != "1" ] then - usage + usage fi type="$(git-cat-file -t "$1" 2>/dev/null)" || @@ -15,6 +27,13 @@ type="$(git-cat-file -t "$1" 2>/dev/null)" || test "$type" = tag || die "$1: cannot verify a non-tag object of type $type." +case "$verbose" in +t) + git-cat-file -p "$1" | + sed -n -e '/^-----BEGIN PGP SIGNATURE-----/q' -e p + ;; +esac + git-cat-file tag "$1" >"$GIT_DIR/.tmp-vtag" || exit 1 cat "$GIT_DIR/.tmp-vtag" | sed '/-----BEGIN PGP/Q' | @@ -12,6 +12,10 @@ #include "git-compat-util.h" #include "exec_cmd.h" +#include "cache.h" +#include "commit.h" +#include "revision.h" + #ifndef PATH_MAX # define PATH_MAX 4096 #endif @@ -245,6 +249,80 @@ static int cmd_help(int argc, char **argv, char **envp) return 0; } +#define LOGSIZE (65536) + +static int cmd_log(int argc, char **argv, char **envp) +{ + struct rev_info rev; + struct commit *commit; + char *buf = xmalloc(LOGSIZE); + static enum cmit_fmt commit_format = CMIT_FMT_DEFAULT; + int abbrev = DEFAULT_ABBREV; + int show_parents = 0; + const char *commit_prefix = "commit "; + + argc = setup_revisions(argc, argv, &rev, "HEAD"); + while (1 < argc) { + char *arg = argv[1]; + if (!strncmp(arg, "--pretty", 8)) { + commit_format = get_commit_format(arg + 8); + if (commit_format == CMIT_FMT_ONELINE) + commit_prefix = ""; + } + else if (!strcmp(arg, "--parents")) { + show_parents = 1; + } + else if (!strcmp(arg, "--no-abbrev")) { + abbrev = 0; + } + else if (!strncmp(arg, "--abbrev=", 9)) { + abbrev = strtoul(arg + 9, NULL, 10); + if (abbrev && abbrev < MINIMUM_ABBREV) + abbrev = MINIMUM_ABBREV; + else if (40 < abbrev) + abbrev = 40; + } + else + die("unrecognized argument: %s", arg); + argc--; argv++; + } + + prepare_revision_walk(&rev); + setup_pager(); + while ((commit = get_revision(&rev)) != NULL) { + printf("%s%s", commit_prefix, + sha1_to_hex(commit->object.sha1)); + if (show_parents) { + struct commit_list *parents = commit->parents; + while (parents) { + struct object *o = &(parents->item->object); + parents = parents->next; + if (o->flags & TMP_MARK) + continue; + printf(" %s", sha1_to_hex(o->sha1)); + o->flags |= TMP_MARK; + } + /* TMP_MARK is a general purpose flag that can + * be used locally, but the user should clean + * things up after it is done with them. + */ + for (parents = commit->parents; + parents; + parents = parents->next) + parents->item->object.flags &= ~TMP_MARK; + } + if (commit_format == CMIT_FMT_ONELINE) + putchar(' '); + else + putchar('\n'); + pretty_print_commit(commit_format, commit, ~0, buf, + LOGSIZE, abbrev); + printf("%s\n", buf); + } + free(buf); + return 0; +} + #define ARRAY_SIZE(x) (sizeof(x)/sizeof(x[0])) static void handle_internal_command(int argc, char **argv, char **envp) @@ -256,6 +334,7 @@ static void handle_internal_command(int argc, char **argv, char **envp) } commands[] = { { "version", cmd_version }, { "help", cmd_help }, + { "log", cmd_log }, }; int i; diff --git a/pack-objects.c b/pack-objects.c index 21ee572f48..136a7f5aad 100644 --- a/pack-objects.c +++ b/pack-objects.c @@ -99,7 +99,7 @@ static int reused_delta = 0; static int pack_revindex_ix(struct packed_git *p) { - unsigned long ui = (unsigned long)(long)p; + unsigned long ui = (unsigned long)p; int i; ui = ui ^ (ui >> 16); /* defeat structure alignment */ diff --git a/pager.c b/pager.c new file mode 100644 index 0000000000..1364e15d23 --- /dev/null +++ b/pager.c @@ -0,0 +1,48 @@ +#include "cache.h" + +/* + * This is split up from the rest of git so that we might do + * something different on Windows, for example. + */ + +static void run_pager(void) +{ + const char *prog = getenv("PAGER"); + if (!prog) + prog = "less"; + setenv("LESS", "-S", 0); + execlp(prog, prog, NULL); +} + +void setup_pager(void) +{ + pid_t pid; + int fd[2]; + + if (!isatty(1)) + return; + if (pipe(fd) < 0) + return; + pid = fork(); + if (pid < 0) { + close(fd[0]); + close(fd[1]); + return; + } + + /* return in the child */ + if (!pid) { + dup2(fd[1], 1); + close(fd[0]); + close(fd[1]); + return; + } + + /* The original process turns into the PAGER */ + dup2(fd[0], 0); + close(fd[0]); + close(fd[1]); + + run_pager(); + exit(255); +} diff --git a/read-tree.c b/read-tree.c index f39fe5ca65..be29b3fe11 100644 --- a/read-tree.c +++ b/read-tree.c @@ -560,9 +560,11 @@ static int threeway_merge(struct cache_entry **stages) */ if ((head_deleted && remote_deleted) || (head_deleted && remote && remote_match) || - (remote_deleted && head && head_match)) + (remote_deleted && head && head_match)) { + if (index) + return deleted_entry(index, index); return 0; - + } /* * Added in both, identically. */ @@ -704,7 +706,7 @@ static int read_cache_unmerged(void) return deleted; } -static const char read_tree_usage[] = "git-read-tree (<sha> | -m [-u | -i] <sha1> [<sha2> [<sha3>]])"; +static const char read_tree_usage[] = "git-read-tree (<sha> | -m [--aggressive] [-u | -i] <sha1> [<sha2> [<sha3>]])"; static struct cache_file cache_file; @@ -151,10 +151,15 @@ static int do_for_each_ref(const char *base, int (*fn)(const char *path, const u break; continue; } - if (read_ref(git_path("%s", path), sha1) < 0) + if (read_ref(git_path("%s", path), sha1) < 0) { + fprintf(stderr, "%s points nowhere!", path); continue; - if (!has_sha1_file(sha1)) + } + if (!has_sha1_file(sha1)) { + fprintf(stderr, "%s does not point to a valid " + "commit object!", path); continue; + } retval = fn(path, sha1); if (retval) break; diff --git a/rev-list.c b/rev-list.c index 67d2a483fc..8e4d83efba 100644 --- a/rev-list.c +++ b/rev-list.c @@ -4,15 +4,12 @@ #include "commit.h" #include "tree.h" #include "blob.h" -#include "epoch.h" #include "diff.h" +#include "revision.h" -#define SEEN (1u << 0) -#define INTERESTING (1u << 1) -#define COUNTED (1u << 2) -#define SHOWN (1u << 3) -#define TREECHANGE (1u << 4) -#define TMP_MARK (1u << 5) /* for isolated cases; clean after use */ +/* bits #0-4 in revision.h */ + +#define COUNTED (1u<<5) static const char rev_list_usage[] = "git-rev-list [OPTION] <commit-id>... [ -- paths... ]\n" @@ -25,7 +22,6 @@ static const char rev_list_usage[] = " --remove-empty\n" " --all\n" " ordering output:\n" -" --merge-order [ --show-breaks ]\n" " --topo-order\n" " --date-order\n" " formatting output:\n" @@ -38,72 +34,18 @@ static const char rev_list_usage[] = " --bisect" ; -static int dense = 1; -static int unpacked = 0; +struct rev_info revs; + static int bisect_list = 0; -static int tag_objects = 0; -static int tree_objects = 0; -static int blob_objects = 0; -static int edge_hint = 0; static int verbose_header = 0; static int abbrev = DEFAULT_ABBREV; static int show_parents = 0; static int hdr_termination = 0; static const char *commit_prefix = ""; -static unsigned long max_age = -1; -static unsigned long min_age = -1; -static int max_count = -1; static enum cmit_fmt commit_format = CMIT_FMT_RAW; -static int merge_order = 0; -static int show_breaks = 0; -static int stop_traversal = 0; -static int topo_order = 0; -static int lifo = 1; -static int no_merges = 0; -static const char **paths = NULL; -static int remove_empty_trees = 0; - -struct name_path { - struct name_path *up; - int elem_len; - const char *elem; -}; - -static char *path_name(struct name_path *path, const char *name) -{ - struct name_path *p; - char *n, *m; - int nlen = strlen(name); - int len = nlen + 1; - - for (p = path; p; p = p->up) { - if (p->elem_len) - len += p->elem_len + 1; - } - n = xmalloc(len); - m = n + len - (nlen + 1); - strcpy(m, name); - for (p = path; p; p = p->up) { - if (p->elem_len) { - m -= p->elem_len + 1; - memcpy(m, p->elem, p->elem_len); - m[p->elem_len] = '/'; - } - } - return n; -} static void show_commit(struct commit *commit) { - commit->object.flags |= SHOWN; - if (show_breaks) { - commit_prefix = "| "; - if (commit->object.flags & DISCONTINUITY) { - commit_prefix = "^ "; - } else if (commit->object.flags & BOUNDARY) { - commit_prefix = "= "; - } - } printf("%s%s", commit_prefix, sha1_to_hex(commit->object.sha1)); if (show_parents) { struct commit_list *parents = commit->parents; @@ -137,86 +79,6 @@ static void show_commit(struct commit *commit) fflush(stdout); } -static int rewrite_one(struct commit **pp) -{ - for (;;) { - struct commit *p = *pp; - if (p->object.flags & (TREECHANGE | UNINTERESTING)) - return 0; - if (!p->parents) - return -1; - *pp = p->parents->item; - } -} - -static void rewrite_parents(struct commit *commit) -{ - struct commit_list **pp = &commit->parents; - while (*pp) { - struct commit_list *parent = *pp; - if (rewrite_one(&parent->item) < 0) { - *pp = parent->next; - continue; - } - pp = &parent->next; - } -} - -static int filter_commit(struct commit * commit) -{ - if (stop_traversal && (commit->object.flags & BOUNDARY)) - return STOP; - if (commit->object.flags & (UNINTERESTING|SHOWN)) - return CONTINUE; - if (min_age != -1 && (commit->date > min_age)) - return CONTINUE; - if (max_age != -1 && (commit->date < max_age)) { - stop_traversal=1; - return CONTINUE; - } - if (no_merges && (commit->parents && commit->parents->next)) - return CONTINUE; - if (paths && dense) { - if (!(commit->object.flags & TREECHANGE)) - return CONTINUE; - rewrite_parents(commit); - } - return DO; -} - -static int process_commit(struct commit * commit) -{ - int action=filter_commit(commit); - - if (action == STOP) { - return STOP; - } - - if (action == CONTINUE) { - return CONTINUE; - } - - if (max_count != -1 && !max_count--) - return STOP; - - show_commit(commit); - - return CONTINUE; -} - -static struct object_list **add_object(struct object *obj, - struct object_list **p, - struct name_path *path, - const char *name) -{ - struct object_list *entry = xmalloc(sizeof(*entry)); - entry->item = obj; - entry->next = *p; - entry->name = path_name(path, name); - *p = entry; - return &entry->next; -} - static struct object_list **process_blob(struct blob *blob, struct object_list **p, struct name_path *path, @@ -224,7 +86,7 @@ static struct object_list **process_blob(struct blob *blob, { struct object *obj = &blob->object; - if (!blob_objects) + if (!revs.blob_objects) return p; if (obj->flags & (UNINTERESTING | SEEN)) return p; @@ -241,7 +103,7 @@ static struct object_list **process_tree(struct tree *tree, struct tree_entry_list *entry; struct name_path me; - if (!tree_objects) + if (!revs.tree_objects) return p; if (obj->flags & (UNINTERESTING | SEEN)) return p; @@ -266,19 +128,16 @@ static struct object_list **process_tree(struct tree *tree, return p; } -static struct object_list *pending_objects = NULL; - -static void show_commit_list(struct commit_list *list) +static void show_commit_list(struct rev_info *revs) { + struct commit *commit; struct object_list *objects = NULL, **p = &objects, *pending; - while (list) { - struct commit *commit = pop_most_recent_commit(&list, SEEN); + while ((commit = get_revision(revs)) != NULL) { p = process_tree(commit->tree, p, NULL, ""); - if (process_commit(commit) == STOP) - break; + show_commit(commit); } - for (pending = pending_objects; pending; pending = pending->next) { + for (pending = revs->pending_objects; pending; pending = pending->next) { struct object *obj = pending->item; const char *name = pending->name; if (obj->flags & (UNINTERESTING | SEEN)) @@ -314,88 +173,6 @@ static void show_commit_list(struct commit_list *list) } } -static void mark_blob_uninteresting(struct blob *blob) -{ - if (!blob_objects) - return; - if (blob->object.flags & UNINTERESTING) - return; - blob->object.flags |= UNINTERESTING; -} - -static void mark_tree_uninteresting(struct tree *tree) -{ - struct object *obj = &tree->object; - struct tree_entry_list *entry; - - if (!tree_objects) - return; - if (obj->flags & UNINTERESTING) - return; - obj->flags |= UNINTERESTING; - if (!has_sha1_file(obj->sha1)) - return; - if (parse_tree(tree) < 0) - die("bad tree %s", sha1_to_hex(obj->sha1)); - entry = tree->entries; - tree->entries = NULL; - while (entry) { - struct tree_entry_list *next = entry->next; - if (entry->directory) - mark_tree_uninteresting(entry->item.tree); - else - mark_blob_uninteresting(entry->item.blob); - free(entry); - entry = next; - } -} - -static void mark_parents_uninteresting(struct commit *commit) -{ - struct commit_list *parents = commit->parents; - - while (parents) { - struct commit *commit = parents->item; - commit->object.flags |= UNINTERESTING; - - /* - * Normally we haven't parsed the parent - * yet, so we won't have a parent of a parent - * here. However, it may turn out that we've - * reached this commit some other way (where it - * wasn't uninteresting), in which case we need - * to mark its parents recursively too.. - */ - if (commit->parents) - mark_parents_uninteresting(commit); - - /* - * A missing commit is ok iff its parent is marked - * uninteresting. - * - * We just mark such a thing parsed, so that when - * it is popped next time around, we won't be trying - * to parse it and get an error. - */ - if (!has_sha1_file(commit->object.sha1)) - commit->object.parsed = 1; - parents = parents->next; - } -} - -static int everybody_uninteresting(struct commit_list *orig) -{ - struct commit_list *list = orig; - while (list) { - struct commit *commit = list->item; - list = list->next; - if (commit->object.flags & UNINTERESTING) - continue; - return 0; - } - return 1; -} - /* * This is a truly stupid algorithm, but it's only * used for bisection, and we just don't care enough. @@ -413,7 +190,7 @@ static int count_distance(struct commit_list *entry) if (commit->object.flags & (UNINTERESTING | COUNTED)) break; - if (!paths || (commit->object.flags & TREECHANGE)) + if (!revs.paths || (commit->object.flags & TREECHANGE)) nr++; commit->object.flags |= COUNTED; p = commit->parents; @@ -447,7 +224,7 @@ static struct commit_list *find_bisection(struct commit_list *list) nr = 0; p = list; while (p) { - if (!paths || (p->item->object.flags & TREECHANGE)) + if (!revs.paths || (p->item->object.flags & TREECHANGE)) nr++; p = p->next; } @@ -457,7 +234,7 @@ static struct commit_list *find_bisection(struct commit_list *list) for (p = list; p; p = p->next) { int distance; - if (paths && !(p->item->object.flags & TREECHANGE)) + if (revs.paths && !(p->item->object.flags & TREECHANGE)) continue; distance = count_distance(p); @@ -483,7 +260,7 @@ static void mark_edge_parents_uninteresting(struct commit *commit) if (!(parent->object.flags & UNINTERESTING)) continue; mark_tree_uninteresting(parent->tree); - if (edge_hint && !(parent->object.flags & SHOWN)) { + if (revs.edge_hint && !(parent->object.flags & SHOWN)) { parent->object.flags |= SHOWN; printf("-%s\n", sha1_to_hex(parent->object.sha1)); } @@ -503,356 +280,29 @@ static void mark_edges_uninteresting(struct commit_list *list) } } -#define TREE_SAME 0 -#define TREE_NEW 1 -#define TREE_DIFFERENT 2 -static int tree_difference = TREE_SAME; - -static void file_add_remove(struct diff_options *options, - int addremove, unsigned mode, - const unsigned char *sha1, - const char *base, const char *path) -{ - int diff = TREE_DIFFERENT; - - /* - * Is it an add of a new file? It means that - * the old tree didn't have it at all, so we - * will turn "TREE_SAME" -> "TREE_NEW", but - * leave any "TREE_DIFFERENT" alone (and if - * it already was "TREE_NEW", we'll keep it - * "TREE_NEW" of course). - */ - if (addremove == '+') { - diff = tree_difference; - if (diff != TREE_SAME) - return; - diff = TREE_NEW; - } - tree_difference = diff; -} - -static void file_change(struct diff_options *options, - unsigned old_mode, unsigned new_mode, - const unsigned char *old_sha1, - const unsigned char *new_sha1, - const char *base, const char *path) -{ - tree_difference = TREE_DIFFERENT; -} - -static struct diff_options diff_opt = { - .recursive = 1, - .add_remove = file_add_remove, - .change = file_change, -}; - -static int compare_tree(struct tree *t1, struct tree *t2) -{ - if (!t1) - return TREE_NEW; - if (!t2) - return TREE_DIFFERENT; - tree_difference = TREE_SAME; - if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0) - return TREE_DIFFERENT; - return tree_difference; -} - -static int same_tree_as_empty(struct tree *t1) -{ - int retval; - void *tree; - struct tree_desc empty, real; - - if (!t1) - return 0; - - tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL); - if (!tree) - return 0; - real.buf = tree; - - empty.buf = ""; - empty.size = 0; - - tree_difference = 0; - retval = diff_tree(&empty, &real, "", &diff_opt); - free(tree); - - return retval >= 0 && !tree_difference; -} - -static void try_to_simplify_commit(struct commit *commit) -{ - struct commit_list **pp, *parent; - - if (!commit->tree) - return; - - if (!commit->parents) { - if (!same_tree_as_empty(commit->tree)) - commit->object.flags |= TREECHANGE; - return; - } - - pp = &commit->parents; - while ((parent = *pp) != NULL) { - struct commit *p = parent->item; - - if (p->object.flags & UNINTERESTING) { - pp = &parent->next; - continue; - } - - parse_commit(p); - switch (compare_tree(p->tree, commit->tree)) { - case TREE_SAME: - parent->next = NULL; - commit->parents = parent; - return; - - case TREE_NEW: - if (remove_empty_trees && same_tree_as_empty(p->tree)) { - *pp = parent->next; - continue; - } - /* fallthrough */ - case TREE_DIFFERENT: - pp = &parent->next; - continue; - } - die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1)); - } - commit->object.flags |= TREECHANGE; -} - -static void add_parents_to_list(struct commit *commit, struct commit_list **list) -{ - struct commit_list *parent = commit->parents; - - /* - * If the commit is uninteresting, don't try to - * prune parents - we want the maximal uninteresting - * set. - * - * Normally we haven't parsed the parent - * yet, so we won't have a parent of a parent - * here. However, it may turn out that we've - * reached this commit some other way (where it - * wasn't uninteresting), in which case we need - * to mark its parents recursively too.. - */ - if (commit->object.flags & UNINTERESTING) { - while (parent) { - struct commit *p = parent->item; - parent = parent->next; - parse_commit(p); - p->object.flags |= UNINTERESTING; - if (p->parents) - mark_parents_uninteresting(p); - if (p->object.flags & SEEN) - continue; - p->object.flags |= SEEN; - insert_by_date(p, list); - } - return; - } - - /* - * Ok, the commit wasn't uninteresting. Try to - * simplify the commit history and find the parent - * that has no differences in the path set if one exists. - */ - if (paths) - try_to_simplify_commit(commit); - - parent = commit->parents; - while (parent) { - struct commit *p = parent->item; - - parent = parent->next; - - parse_commit(p); - if (p->object.flags & SEEN) - continue; - p->object.flags |= SEEN; - insert_by_date(p, list); - } -} - -static struct commit_list *limit_list(struct commit_list *list) -{ - struct commit_list *newlist = NULL; - struct commit_list **p = &newlist; - while (list) { - struct commit_list *entry = list; - struct commit *commit = list->item; - struct object *obj = &commit->object; - - list = list->next; - free(entry); - - if (max_age != -1 && (commit->date < max_age)) - obj->flags |= UNINTERESTING; - if (unpacked && has_sha1_pack(obj->sha1)) - obj->flags |= UNINTERESTING; - add_parents_to_list(commit, &list); - if (obj->flags & UNINTERESTING) { - mark_parents_uninteresting(commit); - if (everybody_uninteresting(list)) - break; - continue; - } - if (min_age != -1 && (commit->date > min_age)) - continue; - p = &commit_list_insert(commit, p)->next; - } - if (tree_objects) - mark_edges_uninteresting(newlist); - if (bisect_list) - newlist = find_bisection(newlist); - return newlist; -} - -static void add_pending_object(struct object *obj, const char *name) -{ - add_object(obj, &pending_objects, NULL, name); -} - -static struct commit *get_commit_reference(const char *name, const unsigned char *sha1, unsigned int flags) -{ - struct object *object; - - object = parse_object(sha1); - if (!object) - die("bad object %s", name); - - /* - * Tag object? Look what it points to.. - */ - while (object->type == tag_type) { - struct tag *tag = (struct tag *) object; - object->flags |= flags; - if (tag_objects && !(object->flags & UNINTERESTING)) - add_pending_object(object, tag->tag); - object = parse_object(tag->tagged->sha1); - if (!object) - die("bad object %s", sha1_to_hex(tag->tagged->sha1)); - } - - /* - * Commit object? Just return it, we'll do all the complex - * reachability crud. - */ - if (object->type == commit_type) { - struct commit *commit = (struct commit *)object; - object->flags |= flags; - if (parse_commit(commit) < 0) - die("unable to parse commit %s", name); - if (flags & UNINTERESTING) - mark_parents_uninteresting(commit); - return commit; - } - - /* - * Tree object? Either mark it uniniteresting, or add it - * to the list of objects to look at later.. - */ - if (object->type == tree_type) { - struct tree *tree = (struct tree *)object; - if (!tree_objects) - return NULL; - if (flags & UNINTERESTING) { - mark_tree_uninteresting(tree); - return NULL; - } - add_pending_object(object, ""); - return NULL; - } - - /* - * Blob object? You know the drill by now.. - */ - if (object->type == blob_type) { - struct blob *blob = (struct blob *)object; - if (!blob_objects) - return NULL; - if (flags & UNINTERESTING) { - mark_blob_uninteresting(blob); - return NULL; - } - add_pending_object(object, ""); - return NULL; - } - die("%s is unknown object", name); -} - -static void handle_one_commit(struct commit *com, struct commit_list **lst) -{ - if (!com || com->object.flags & SEEN) - return; - com->object.flags |= SEEN; - commit_list_insert(com, lst); -} - -/* for_each_ref() callback does not allow user data -- Yuck. */ -static struct commit_list **global_lst; - -static int include_one_commit(const char *path, const unsigned char *sha1) -{ - struct commit *com = get_commit_reference(path, sha1, 0); - handle_one_commit(com, global_lst); - return 0; -} - -static void handle_all(struct commit_list **lst) -{ - global_lst = lst; - for_each_ref(include_one_commit); - global_lst = NULL; -} - int main(int argc, const char **argv) { - const char *prefix = setup_git_directory(); - struct commit_list *list = NULL; - int i, limited = 0; + struct commit_list *list; + int i; + + argc = setup_revisions(argc, argv, &revs, NULL); for (i = 1 ; i < argc; i++) { - int flags; const char *arg = argv[i]; - char *dotdot; - struct commit *commit; - unsigned char sha1[20]; /* accept -<digit>, like traditilnal "head" */ if ((*arg == '-') && isdigit(arg[1])) { - max_count = atoi(arg + 1); + revs.max_count = atoi(arg + 1); continue; } if (!strcmp(arg, "-n")) { if (++i >= argc) die("-n requires an argument"); - max_count = atoi(argv[i]); + revs.max_count = atoi(argv[i]); continue; } if (!strncmp(arg,"-n",2)) { - max_count = atoi(arg + 2); - continue; - } - if (!strncmp(arg, "--max-count=", 12)) { - max_count = atoi(arg + 12); - continue; - } - if (!strncmp(arg, "--max-age=", 10)) { - max_age = atoi(arg + 10); - limited = 1; - continue; - } - if (!strncmp(arg, "--min-age=", 10)) { - min_age = atoi(arg + 10); - limited = 1; + revs.max_count = atoi(arg + 2); continue; } if (!strcmp(arg, "--header")) { @@ -881,10 +331,6 @@ int main(int argc, const char **argv) commit_prefix = "commit "; continue; } - if (!strncmp(arg, "--no-merges", 11)) { - no_merges = 1; - continue; - } if (!strcmp(arg, "--parents")) { show_parents = 1; continue; @@ -893,140 +339,27 @@ int main(int argc, const char **argv) bisect_list = 1; continue; } - if (!strcmp(arg, "--all")) { - handle_all(&list); - continue; - } - if (!strcmp(arg, "--objects")) { - tag_objects = 1; - tree_objects = 1; - blob_objects = 1; - continue; - } - if (!strcmp(arg, "--objects-edge")) { - tag_objects = 1; - tree_objects = 1; - blob_objects = 1; - edge_hint = 1; - continue; - } - if (!strcmp(arg, "--unpacked")) { - unpacked = 1; - limited = 1; - continue; - } - if (!strcmp(arg, "--merge-order")) { - merge_order = 1; - continue; - } - if (!strcmp(arg, "--show-breaks")) { - show_breaks = 1; - continue; - } - if (!strcmp(arg, "--topo-order")) { - topo_order = 1; - lifo = 1; - limited = 1; - continue; - } - if (!strcmp(arg, "--date-order")) { - topo_order = 1; - lifo = 0; - limited = 1; - continue; - } - if (!strcmp(arg, "--dense")) { - dense = 1; - continue; - } - if (!strcmp(arg, "--sparse")) { - dense = 0; - continue; - } - if (!strcmp(arg, "--remove-empty")) { - remove_empty_trees = 1; - continue; - } - if (!strcmp(arg, "--")) { - i++; - break; - } - - if (show_breaks && !merge_order) - usage(rev_list_usage); + usage(rev_list_usage); - flags = 0; - dotdot = strstr(arg, ".."); - if (dotdot) { - unsigned char from_sha1[20]; - char *next = dotdot + 2; - *dotdot = 0; - if (!*next) - next = "HEAD"; - if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) { - struct commit *exclude; - struct commit *include; - - exclude = get_commit_reference(arg, from_sha1, UNINTERESTING); - include = get_commit_reference(next, sha1, 0); - if (!exclude || !include) - die("Invalid revision range %s..%s", arg, next); - limited = 1; - handle_one_commit(exclude, &list); - handle_one_commit(include, &list); - continue; - } - *dotdot = '.'; - } - if (*arg == '^') { - flags = UNINTERESTING; - arg++; - limited = 1; - } - if (get_sha1(arg, sha1) < 0) { - struct stat st; - if (lstat(arg, &st) < 0) - die("'%s': %s", arg, strerror(errno)); - break; - } - commit = get_commit_reference(arg, sha1, flags); - handle_one_commit(commit, &list); } + list = revs.commits; + if (!list && - (!(tag_objects||tree_objects||blob_objects) && !pending_objects)) + (!(revs.tag_objects||revs.tree_objects||revs.blob_objects) && !revs.pending_objects)) usage(rev_list_usage); - paths = get_pathspec(prefix, argv + i); - if (paths) { - limited = 1; - diff_tree_setup_paths(paths); - } + prepare_revision_walk(&revs); + if (revs.tree_objects) + mark_edges_uninteresting(revs.commits); + + if (bisect_list) + revs.commits = find_bisection(revs.commits); save_commit_buffer = verbose_header; track_object_refs = 0; - if (!merge_order) { - sort_by_date(&list); - if (list && !limited && max_count == 1 && - !tag_objects && !tree_objects && !blob_objects) { - show_commit(list->item); - return 0; - } - if (limited) - list = limit_list(list); - if (topo_order) - sort_in_topological_order(&list, lifo); - show_commit_list(list); - } else { -#ifndef NO_OPENSSL - if (sort_list_in_merge_order(list, &process_commit)) { - die("merge order sort failed\n"); - } -#else - die("merge order sort unsupported, OpenSSL not linked"); -#endif - } + show_commit_list(&revs); return 0; } diff --git a/rev-parse.c b/rev-parse.c index 610eacb35a..f90e999e60 100644 --- a/rev-parse.c +++ b/rev-parse.c @@ -39,14 +39,12 @@ static int is_rev_argument(const char *arg) "--header", "--max-age=", "--max-count=", - "--merge-order", "--min-age=", "--no-merges", "--objects", "--objects-edge", "--parents", "--pretty", - "--show-breaks", "--sparse", "--topo-order", "--date-order", diff --git a/revision.c b/revision.c new file mode 100644 index 0000000000..a3df810076 --- /dev/null +++ b/revision.c @@ -0,0 +1,722 @@ +#include "cache.h" +#include "tag.h" +#include "blob.h" +#include "tree.h" +#include "commit.h" +#include "diff.h" +#include "refs.h" +#include "revision.h" + +static char *path_name(struct name_path *path, const char *name) +{ + struct name_path *p; + char *n, *m; + int nlen = strlen(name); + int len = nlen + 1; + + for (p = path; p; p = p->up) { + if (p->elem_len) + len += p->elem_len + 1; + } + n = xmalloc(len); + m = n + len - (nlen + 1); + strcpy(m, name); + for (p = path; p; p = p->up) { + if (p->elem_len) { + m -= p->elem_len + 1; + memcpy(m, p->elem, p->elem_len); + m[p->elem_len] = '/'; + } + } + return n; +} + +struct object_list **add_object(struct object *obj, + struct object_list **p, + struct name_path *path, + const char *name) +{ + struct object_list *entry = xmalloc(sizeof(*entry)); + entry->item = obj; + entry->next = *p; + entry->name = path_name(path, name); + *p = entry; + return &entry->next; +} + +static void mark_blob_uninteresting(struct blob *blob) +{ + if (blob->object.flags & UNINTERESTING) + return; + blob->object.flags |= UNINTERESTING; +} + +void mark_tree_uninteresting(struct tree *tree) +{ + struct object *obj = &tree->object; + struct tree_entry_list *entry; + + if (obj->flags & UNINTERESTING) + return; + obj->flags |= UNINTERESTING; + if (!has_sha1_file(obj->sha1)) + return; + if (parse_tree(tree) < 0) + die("bad tree %s", sha1_to_hex(obj->sha1)); + entry = tree->entries; + tree->entries = NULL; + while (entry) { + struct tree_entry_list *next = entry->next; + if (entry->directory) + mark_tree_uninteresting(entry->item.tree); + else + mark_blob_uninteresting(entry->item.blob); + free(entry); + entry = next; + } +} + +void mark_parents_uninteresting(struct commit *commit) +{ + struct commit_list *parents = commit->parents; + + while (parents) { + struct commit *commit = parents->item; + commit->object.flags |= UNINTERESTING; + + /* + * Normally we haven't parsed the parent + * yet, so we won't have a parent of a parent + * here. However, it may turn out that we've + * reached this commit some other way (where it + * wasn't uninteresting), in which case we need + * to mark its parents recursively too.. + */ + if (commit->parents) + mark_parents_uninteresting(commit); + + /* + * A missing commit is ok iff its parent is marked + * uninteresting. + * + * We just mark such a thing parsed, so that when + * it is popped next time around, we won't be trying + * to parse it and get an error. + */ + if (!has_sha1_file(commit->object.sha1)) + commit->object.parsed = 1; + parents = parents->next; + } +} + +static void add_pending_object(struct rev_info *revs, struct object *obj, const char *name) +{ + add_object(obj, &revs->pending_objects, NULL, name); +} + +static struct commit *get_commit_reference(struct rev_info *revs, const char *name, const unsigned char *sha1, unsigned int flags) +{ + struct object *object; + + object = parse_object(sha1); + if (!object) + die("bad object %s", name); + + /* + * Tag object? Look what it points to.. + */ + while (object->type == tag_type) { + struct tag *tag = (struct tag *) object; + object->flags |= flags; + if (revs->tag_objects && !(object->flags & UNINTERESTING)) + add_pending_object(revs, object, tag->tag); + object = parse_object(tag->tagged->sha1); + if (!object) + die("bad object %s", sha1_to_hex(tag->tagged->sha1)); + } + + /* + * Commit object? Just return it, we'll do all the complex + * reachability crud. + */ + if (object->type == commit_type) { + struct commit *commit = (struct commit *)object; + object->flags |= flags; + if (parse_commit(commit) < 0) + die("unable to parse commit %s", name); + if (flags & UNINTERESTING) { + mark_parents_uninteresting(commit); + revs->limited = 1; + } + return commit; + } + + /* + * Tree object? Either mark it uniniteresting, or add it + * to the list of objects to look at later.. + */ + if (object->type == tree_type) { + struct tree *tree = (struct tree *)object; + if (!revs->tree_objects) + return NULL; + if (flags & UNINTERESTING) { + mark_tree_uninteresting(tree); + return NULL; + } + add_pending_object(revs, object, ""); + return NULL; + } + + /* + * Blob object? You know the drill by now.. + */ + if (object->type == blob_type) { + struct blob *blob = (struct blob *)object; + if (!revs->blob_objects) + return NULL; + if (flags & UNINTERESTING) { + mark_blob_uninteresting(blob); + return NULL; + } + add_pending_object(revs, object, ""); + return NULL; + } + die("%s is unknown object", name); +} + +static int everybody_uninteresting(struct commit_list *orig) +{ + struct commit_list *list = orig; + while (list) { + struct commit *commit = list->item; + list = list->next; + if (commit->object.flags & UNINTERESTING) + continue; + return 0; + } + return 1; +} + +#define TREE_SAME 0 +#define TREE_NEW 1 +#define TREE_DIFFERENT 2 +static int tree_difference = TREE_SAME; + +static void file_add_remove(struct diff_options *options, + int addremove, unsigned mode, + const unsigned char *sha1, + const char *base, const char *path) +{ + int diff = TREE_DIFFERENT; + + /* + * Is it an add of a new file? It means that + * the old tree didn't have it at all, so we + * will turn "TREE_SAME" -> "TREE_NEW", but + * leave any "TREE_DIFFERENT" alone (and if + * it already was "TREE_NEW", we'll keep it + * "TREE_NEW" of course). + */ + if (addremove == '+') { + diff = tree_difference; + if (diff != TREE_SAME) + return; + diff = TREE_NEW; + } + tree_difference = diff; +} + +static void file_change(struct diff_options *options, + unsigned old_mode, unsigned new_mode, + const unsigned char *old_sha1, + const unsigned char *new_sha1, + const char *base, const char *path) +{ + tree_difference = TREE_DIFFERENT; +} + +static struct diff_options diff_opt = { + .recursive = 1, + .add_remove = file_add_remove, + .change = file_change, +}; + +static int compare_tree(struct tree *t1, struct tree *t2) +{ + if (!t1) + return TREE_NEW; + if (!t2) + return TREE_DIFFERENT; + tree_difference = TREE_SAME; + if (diff_tree_sha1(t1->object.sha1, t2->object.sha1, "", &diff_opt) < 0) + return TREE_DIFFERENT; + return tree_difference; +} + +static int same_tree_as_empty(struct tree *t1) +{ + int retval; + void *tree; + struct tree_desc empty, real; + + if (!t1) + return 0; + + tree = read_object_with_reference(t1->object.sha1, "tree", &real.size, NULL); + if (!tree) + return 0; + real.buf = tree; + + empty.buf = ""; + empty.size = 0; + + tree_difference = 0; + retval = diff_tree(&empty, &real, "", &diff_opt); + free(tree); + + return retval >= 0 && !tree_difference; +} + +static void try_to_simplify_commit(struct rev_info *revs, struct commit *commit) +{ + struct commit_list **pp, *parent; + + if (!commit->tree) + return; + + if (!commit->parents) { + if (!same_tree_as_empty(commit->tree)) + commit->object.flags |= TREECHANGE; + return; + } + + pp = &commit->parents; + while ((parent = *pp) != NULL) { + struct commit *p = parent->item; + + if (p->object.flags & UNINTERESTING) { + pp = &parent->next; + continue; + } + + parse_commit(p); + switch (compare_tree(p->tree, commit->tree)) { + case TREE_SAME: + parent->next = NULL; + commit->parents = parent; + return; + + case TREE_NEW: + if (revs->remove_empty_trees && same_tree_as_empty(p->tree)) { + *pp = parent->next; + continue; + } + /* fallthrough */ + case TREE_DIFFERENT: + pp = &parent->next; + continue; + } + die("bad tree compare for commit %s", sha1_to_hex(commit->object.sha1)); + } + commit->object.flags |= TREECHANGE; +} + +static void add_parents_to_list(struct rev_info *revs, struct commit *commit, struct commit_list **list) +{ + struct commit_list *parent = commit->parents; + + /* + * If the commit is uninteresting, don't try to + * prune parents - we want the maximal uninteresting + * set. + * + * Normally we haven't parsed the parent + * yet, so we won't have a parent of a parent + * here. However, it may turn out that we've + * reached this commit some other way (where it + * wasn't uninteresting), in which case we need + * to mark its parents recursively too.. + */ + if (commit->object.flags & UNINTERESTING) { + while (parent) { + struct commit *p = parent->item; + parent = parent->next; + parse_commit(p); + p->object.flags |= UNINTERESTING; + if (p->parents) + mark_parents_uninteresting(p); + if (p->object.flags & SEEN) + continue; + p->object.flags |= SEEN; + insert_by_date(p, list); + } + return; + } + + /* + * Ok, the commit wasn't uninteresting. Try to + * simplify the commit history and find the parent + * that has no differences in the path set if one exists. + */ + if (revs->paths) + try_to_simplify_commit(revs, commit); + + parent = commit->parents; + while (parent) { + struct commit *p = parent->item; + + parent = parent->next; + + parse_commit(p); + if (p->object.flags & SEEN) + continue; + p->object.flags |= SEEN; + insert_by_date(p, list); + } +} + +static void limit_list(struct rev_info *revs) +{ + struct commit_list *list = revs->commits; + struct commit_list *newlist = NULL; + struct commit_list **p = &newlist; + + if (revs->paths) + diff_tree_setup_paths(revs->paths); + + while (list) { + struct commit_list *entry = list; + struct commit *commit = list->item; + struct object *obj = &commit->object; + + list = list->next; + free(entry); + + if (revs->max_age != -1 && (commit->date < revs->max_age)) + obj->flags |= UNINTERESTING; + if (revs->unpacked && has_sha1_pack(obj->sha1)) + obj->flags |= UNINTERESTING; + add_parents_to_list(revs, commit, &list); + if (obj->flags & UNINTERESTING) { + mark_parents_uninteresting(commit); + if (everybody_uninteresting(list)) + break; + continue; + } + if (revs->min_age != -1 && (commit->date > revs->min_age)) + continue; + p = &commit_list_insert(commit, p)->next; + } + revs->commits = newlist; +} + +static void add_one_commit(struct commit *commit, struct rev_info *revs) +{ + if (!commit || (commit->object.flags & SEEN)) + return; + commit->object.flags |= SEEN; + commit_list_insert(commit, &revs->commits); +} + +static int all_flags; +static struct rev_info *all_revs; + +static int handle_one_ref(const char *path, const unsigned char *sha1) +{ + struct commit *commit = get_commit_reference(all_revs, path, sha1, all_flags); + add_one_commit(commit, all_revs); + return 0; +} + +static void handle_all(struct rev_info *revs, unsigned flags) +{ + all_revs = revs; + all_flags = flags; + for_each_ref(handle_one_ref); +} + +/* + * Parse revision information, filling in the "rev_info" structure, + * and removing the used arguments from the argument list. + * + * Returns the number of arguments left that weren't recognized + * (which are also moved to the head of the argument list) + */ +int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def) +{ + int i, flags, seen_dashdash; + const char **unrecognized = argv + 1; + int left = 1; + + memset(revs, 0, sizeof(*revs)); + revs->lifo = 1; + revs->dense = 1; + revs->prefix = setup_git_directory(); + revs->max_age = -1; + revs->min_age = -1; + revs->max_count = -1; + + /* First, search for "--" */ + seen_dashdash = 0; + for (i = 1; i < argc; i++) { + const char *arg = argv[i]; + if (strcmp(arg, "--")) + continue; + argv[i] = NULL; + argc = i; + revs->paths = get_pathspec(revs->prefix, argv + i + 1); + seen_dashdash = 1; + break; + } + + flags = 0; + for (i = 1; i < argc; i++) { + struct commit *commit; + const char *arg = argv[i]; + unsigned char sha1[20]; + char *dotdot; + int local_flags; + + if (*arg == '-') { + if (!strncmp(arg, "--max-count=", 12)) { + revs->max_count = atoi(arg + 12); + continue; + } + /* accept -<digit>, like traditilnal "head" */ + if ((*arg == '-') && isdigit(arg[1])) { + revs->max_count = atoi(arg + 1); + continue; + } + if (!strcmp(arg, "-n")) { + if (argc <= i + 1) + die("-n requires an argument"); + revs->max_count = atoi(argv[++i]); + continue; + } + if (!strncmp(arg,"-n",2)) { + revs->max_count = atoi(arg + 2); + continue; + } + if (!strncmp(arg, "--max-age=", 10)) { + revs->max_age = atoi(arg + 10); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--min-age=", 10)) { + revs->min_age = atoi(arg + 10); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--since=", 8)) { + revs->max_age = approxidate(arg + 8); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--after=", 8)) { + revs->max_age = approxidate(arg + 8); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--before=", 9)) { + revs->min_age = approxidate(arg + 9); + revs->limited = 1; + continue; + } + if (!strncmp(arg, "--until=", 8)) { + revs->min_age = approxidate(arg + 8); + revs->limited = 1; + continue; + } + if (!strcmp(arg, "--all")) { + handle_all(revs, flags); + continue; + } + if (!strcmp(arg, "--not")) { + flags ^= UNINTERESTING; + continue; + } + if (!strcmp(arg, "--default")) { + if (++i >= argc) + die("bad --default argument"); + def = argv[i]; + continue; + } + if (!strcmp(arg, "--topo-order")) { + revs->topo_order = 1; + revs->limited = 1; + continue; + } + if (!strcmp(arg, "--date-order")) { + revs->lifo = 0; + revs->topo_order = 1; + revs->limited = 1; + continue; + } + if (!strcmp(arg, "--dense")) { + revs->dense = 1; + continue; + } + if (!strcmp(arg, "--sparse")) { + revs->dense = 0; + continue; + } + if (!strcmp(arg, "--remove-empty")) { + revs->remove_empty_trees = 1; + continue; + } + if (!strncmp(arg, "--no-merges", 11)) { + revs->no_merges = 1; + continue; + } + if (!strcmp(arg, "--objects")) { + revs->tag_objects = 1; + revs->tree_objects = 1; + revs->blob_objects = 1; + continue; + } + if (!strcmp(arg, "--objects-edge")) { + revs->tag_objects = 1; + revs->tree_objects = 1; + revs->blob_objects = 1; + revs->edge_hint = 1; + continue; + } + if (!strcmp(arg, "--unpacked")) { + revs->unpacked = 1; + revs->limited = 1; + continue; + } + *unrecognized++ = arg; + left++; + continue; + } + dotdot = strstr(arg, ".."); + if (dotdot) { + unsigned char from_sha1[20]; + char *next = dotdot + 2; + *dotdot = 0; + if (!*next) + next = "HEAD"; + if (!get_sha1(arg, from_sha1) && !get_sha1(next, sha1)) { + struct commit *exclude; + struct commit *include; + + exclude = get_commit_reference(revs, arg, from_sha1, flags ^ UNINTERESTING); + include = get_commit_reference(revs, next, sha1, flags); + if (!exclude || !include) + die("Invalid revision range %s..%s", arg, next); + add_one_commit(exclude, revs); + add_one_commit(include, revs); + continue; + } + *dotdot = '.'; + } + local_flags = 0; + if (*arg == '^') { + local_flags = UNINTERESTING; + arg++; + } + if (get_sha1(arg, sha1) < 0) { + struct stat st; + int j; + + if (seen_dashdash || local_flags) + die("bad revision '%s'", arg); + + /* If we didn't have a "--", all filenames must exist */ + for (j = i; j < argc; j++) { + if (lstat(argv[j], &st) < 0) + die("'%s': %s", arg, strerror(errno)); + } + revs->paths = get_pathspec(revs->prefix, argv + i); + break; + } + commit = get_commit_reference(revs, arg, sha1, flags ^ local_flags); + add_one_commit(commit, revs); + } + if (def && !revs->commits) { + unsigned char sha1[20]; + struct commit *commit; + if (get_sha1(def, sha1) < 0) + die("bad default revision '%s'", def); + commit = get_commit_reference(revs, def, sha1, 0); + add_one_commit(commit, revs); + } + if (revs->paths) + revs->limited = 1; + return left; +} + +void prepare_revision_walk(struct rev_info *revs) +{ + sort_by_date(&revs->commits); + if (revs->limited) + limit_list(revs); + if (revs->topo_order) + sort_in_topological_order(&revs->commits, revs->lifo); +} + +static int rewrite_one(struct commit **pp) +{ + for (;;) { + struct commit *p = *pp; + if (p->object.flags & (TREECHANGE | UNINTERESTING)) + return 0; + if (!p->parents) + return -1; + *pp = p->parents->item; + } +} + +static void rewrite_parents(struct commit *commit) +{ + struct commit_list **pp = &commit->parents; + while (*pp) { + struct commit_list *parent = *pp; + if (rewrite_one(&parent->item) < 0) { + *pp = parent->next; + continue; + } + pp = &parent->next; + } +} + +struct commit *get_revision(struct rev_info *revs) +{ + struct commit_list *list = revs->commits; + struct commit *commit; + + if (!list) + return NULL; + + /* Check the max_count ... */ + commit = list->item; + switch (revs->max_count) { + case -1: + break; + case 0: + return NULL; + default: + revs->max_count--; + } + + do { + commit = pop_most_recent_commit(&revs->commits, SEEN); + if (commit->object.flags & (UNINTERESTING|SHOWN)) + continue; + if (revs->min_age != -1 && (commit->date > revs->min_age)) + continue; + if (revs->max_age != -1 && (commit->date < revs->max_age)) + return NULL; + if (revs->no_merges && commit->parents && commit->parents->next) + continue; + if (revs->paths && revs->dense) { + if (!(commit->object.flags & TREECHANGE)) + continue; + rewrite_parents(commit); + } + commit->object.flags |= SHOWN; + return commit; + } while (revs->commits); + return NULL; +} diff --git a/revision.h b/revision.h new file mode 100644 index 0000000000..31e8f61567 --- /dev/null +++ b/revision.h @@ -0,0 +1,57 @@ +#ifndef REVISION_H +#define REVISION_H + +#define SEEN (1u<<0) +#define UNINTERESTING (1u<<1) +#define TREECHANGE (1u<<2) +#define SHOWN (1u<<3) +#define TMP_MARK (1u<<4) /* for isolated cases; clean after use */ + +struct rev_info { + /* Starting list */ + struct commit_list *commits; + struct object_list *pending_objects; + + /* Basic information */ + const char *prefix; + const char **paths; + + /* Traversal flags */ + unsigned int dense:1, + no_merges:1, + remove_empty_trees:1, + lifo:1, + topo_order:1, + tag_objects:1, + tree_objects:1, + blob_objects:1, + edge_hint:1, + limited:1, + unpacked:1; + + /* special limits */ + int max_count; + unsigned long max_age; + unsigned long min_age; +}; + +/* revision.c */ +extern int setup_revisions(int argc, const char **argv, struct rev_info *revs, const char *def); +extern void prepare_revision_walk(struct rev_info *revs); +extern struct commit *get_revision(struct rev_info *revs); + +extern void mark_parents_uninteresting(struct commit *commit); +extern void mark_tree_uninteresting(struct tree *tree); + +struct name_path { + struct name_path *up; + int elem_len; + const char *elem; +}; + +extern struct object_list **add_object(struct object *obj, + struct object_list **p, + struct name_path *path, + const char *name); + +#endif diff --git a/show-branch.c b/show-branch.c index 5a86ae2f9e..24efb65e62 100644 --- a/show-branch.c +++ b/show-branch.c @@ -5,7 +5,7 @@ #include "refs.h" static const char show_branch_usage[] = -"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [<refs>...]"; +"git-show-branch [--current] [--all] [--heads] [--tags] [--topo-order] [--more=count | --list | --independent | --merge-base ] [--topics] [<refs>...]"; static int default_num = 0; static int default_alloc = 0; @@ -547,6 +547,7 @@ int main(int ac, char **av) int shown_merge_point = 0; int with_current_branch = 0; int head_at = -1; + int topics = 0; setup_git_directory(); git_config(git_show_branch_config); @@ -587,6 +588,8 @@ int main(int ac, char **av) independent = 1; else if (!strcmp(arg, "--topo-order")) lifo = 1; + else if (!strcmp(arg, "--topics")) + topics = 1; else if (!strcmp(arg, "--date-order")) lifo = 0; else @@ -724,11 +727,17 @@ int main(int ac, char **av) while (seen) { struct commit *commit = pop_one_commit(&seen); int this_flag = commit->object.flags; + int is_merge_point = ((this_flag & all_revs) == all_revs); - shown_merge_point |= ((this_flag & all_revs) == all_revs); + shown_merge_point |= is_merge_point; if (1 < num_rev) { int is_merge = !!(commit->parents && commit->parents->next); + if (topics && + !is_merge_point && + (this_flag & (1u << REV_SHIFT))) + continue; + for (i = 0; i < num_rev; i++) { int mark; if (!(this_flag & (1u << (i + REV_SHIFT)))) diff --git a/t/t3600-rm.sh b/t/t3600-rm.sh index cabfadd56d..d1947e11c1 100755 --- a/t/t3600-rm.sh +++ b/t/t3600-rm.sh @@ -8,11 +8,20 @@ test_description='Test of the various options to git-rm.' . ./test-lib.sh # Setup some files to be removed, some with funny characters -touch -- foo bar baz 'space embedded' 'tab embedded' 'newline -embedded' -q -git-add -- foo bar baz 'space embedded' 'tab embedded' 'newline -embedded' -q -git-commit -m "add files" +touch -- foo bar baz 'space embedded' -q +git-add -- foo bar baz 'space embedded' -q +git-commit -m "add normal files" +test_tabs=y +if touch -- 'tab embedded' 'newline +embedded' +then +git-add -- 'tab embedded' 'newline +embedded' +git-commit -m "add files with tabs and newlines" +else + say 'Your filesystem does not allow tabs in filenames.' + test_tabs=n +fi test_expect_success \ 'Pre-check that foo exists and is in index before git-rm foo' \ @@ -42,16 +51,18 @@ test_expect_success \ 'Test that "git-rm -- -q" succeeds (remove a file that looks like an option)' \ 'git-rm -- -q' -test_expect_success \ +test "$test_tabs" = y && test_expect_success \ "Test that \"git-rm -f\" succeeds with embedded space, tab, or newline characters." \ "git-rm -f 'space embedded' 'tab embedded' 'newline embedded'" +if test "$test_tabs" = y; then chmod u-w . test_expect_failure \ 'Test that "git-rm -f" fails if its rm fails' \ 'git-rm -f baz' chmod u+w . +fi test_expect_success \ 'When the rm in "git-rm -f" fails, it should not remove the file from the index' \ diff --git a/t/t6001-rev-list-merge-order.sh b/t/t6001-rev-list-merge-order.sh deleted file mode 100755 index 7724e8a8c5..0000000000 --- a/t/t6001-rev-list-merge-order.sh +++ /dev/null @@ -1,462 +0,0 @@ -#!/bin/sh -# -# Copyright (c) 2005 Jon Seymour -# - -test_description='Tests git-rev-list --merge-order functionality' - -. ./test-lib.sh -. ../t6000lib.sh # t6xxx specific functions - -# test-case specific test function -check_adjacency() -{ - read previous - echo "= $previous" - while read next - do - if ! (git-cat-file commit $previous | grep "^parent $next" >/dev/null) - then - echo "^ $next" - else - echo "| $next" - fi - previous=$next - done -} - -list_duplicates() -{ - "$@" | sort | uniq -d -} - -grep_stderr() -{ - args=$1 - shift 1 - "$@" 2>&1 | grep "$args" -} - -date >path0 -git-update-index --add path0 -save_tag tree git-write-tree -on_committer_date "1971-08-16 00:00:00" hide_error save_tag root unique_commit root tree -on_committer_date "1971-08-16 00:00:01" save_tag l0 unique_commit l0 tree -p root -on_committer_date "1971-08-16 00:00:02" save_tag l1 unique_commit l1 tree -p l0 -on_committer_date "1971-08-16 00:00:03" save_tag l2 unique_commit l2 tree -p l1 -on_committer_date "1971-08-16 00:00:04" save_tag a0 unique_commit a0 tree -p l2 -on_committer_date "1971-08-16 00:00:05" save_tag a1 unique_commit a1 tree -p a0 -on_committer_date "1971-08-16 00:00:06" save_tag b1 unique_commit b1 tree -p a0 -on_committer_date "1971-08-16 00:00:07" save_tag c1 unique_commit c1 tree -p b1 -on_committer_date "1971-08-16 00:00:08" as_author foobar@example.com save_tag b2 unique_commit b2 tree -p b1 -on_committer_date "1971-08-16 00:00:09" save_tag b3 unique_commit b2 tree -p b2 -on_committer_date "1971-08-16 00:00:10" save_tag c2 unique_commit c2 tree -p c1 -p b2 -on_committer_date "1971-08-16 00:00:11" save_tag c3 unique_commit c3 tree -p c2 -on_committer_date "1971-08-16 00:00:12" save_tag a2 unique_commit a2 tree -p a1 -on_committer_date "1971-08-16 00:00:13" save_tag a3 unique_commit a3 tree -p a2 -on_committer_date "1971-08-16 00:00:14" save_tag b4 unique_commit b4 tree -p b3 -p a3 -on_committer_date "1971-08-16 00:00:15" save_tag a4 unique_commit a4 tree -p a3 -p b4 -p c3 -on_committer_date "1971-08-16 00:00:16" save_tag l3 unique_commit l3 tree -p a4 -on_committer_date "1971-08-16 00:00:17" save_tag l4 unique_commit l4 tree -p l3 -on_committer_date "1971-08-16 00:00:18" save_tag l5 unique_commit l5 tree -p l4 -on_committer_date "1971-08-16 00:00:19" save_tag m1 unique_commit m1 tree -p a4 -p c3 -on_committer_date "1971-08-16 00:00:20" save_tag m2 unique_commit m2 tree -p c3 -p a4 -on_committer_date "1971-08-16 00:00:21" hide_error save_tag alt_root unique_commit alt_root tree -on_committer_date "1971-08-16 00:00:22" save_tag r0 unique_commit r0 tree -p alt_root -on_committer_date "1971-08-16 00:00:23" save_tag r1 unique_commit r1 tree -p r0 -on_committer_date "1971-08-16 00:00:24" save_tag l5r1 unique_commit l5r1 tree -p l5 -p r1 -on_committer_date "1971-08-16 00:00:25" save_tag r1l5 unique_commit r1l5 tree -p r1 -p l5 - - -# -# note: as of 20/6, it isn't possible to create duplicate parents, so this -# can't be tested. -# -#on_committer_date "1971-08-16 00:00:20" save_tag m3 unique_commit m3 tree -p c3 -p a4 -p c3 -hide_error save_tag e1 as_author e@example.com unique_commit e1 tree -save_tag e2 as_author e@example.com unique_commit e2 tree -p e1 -save_tag f1 as_author f@example.com unique_commit f1 tree -p e1 -save_tag e3 as_author e@example.com unique_commit e3 tree -p e2 -save_tag f2 as_author f@example.com unique_commit f2 tree -p f1 -save_tag e4 as_author e@example.com unique_commit e4 tree -p e3 -p f2 -save_tag e5 as_author e@example.com unique_commit e5 tree -p e4 -save_tag f3 as_author f@example.com unique_commit f3 tree -p f2 -save_tag f4 as_author f@example.com unique_commit f4 tree -p f3 -save_tag e6 as_author e@example.com unique_commit e6 tree -p e5 -p f4 -save_tag f5 as_author f@example.com unique_commit f5 tree -p f4 -save_tag f6 as_author f@example.com unique_commit f6 tree -p f5 -p e6 -save_tag e7 as_author e@example.com unique_commit e7 tree -p e6 -save_tag e8 as_author e@example.com unique_commit e8 tree -p e7 -save_tag e9 as_author e@example.com unique_commit e9 tree -p e8 -save_tag f7 as_author f@example.com unique_commit f7 tree -p f6 -save_tag f8 as_author f@example.com unique_commit f8 tree -p f7 -save_tag f9 as_author f@example.com unique_commit f9 tree -p f8 -save_tag e10 as_author e@example.com unique_commit e1 tree -p e9 -p f8 - -hide_error save_tag g0 unique_commit g0 tree -save_tag g1 unique_commit g1 tree -p g0 -save_tag h1 unique_commit g2 tree -p g0 -save_tag g2 unique_commit g3 tree -p g1 -p h1 -save_tag h2 unique_commit g4 tree -p g2 -save_tag g3 unique_commit g5 tree -p g2 -save_tag g4 unique_commit g6 tree -p g3 -p h2 - -git-update-ref HEAD $(tag l5) - -test_output_expect_success 'rev-list has correct number of entries' 'git-rev-list HEAD | wc -l | tr -d \" \"' <<EOF -19 -EOF - -if git-rev-list --merge-order HEAD 2>&1 | grep 'OpenSSL not linked' >/dev/null -then - test_expect_success 'skipping merge-order test' : - test_done - exit -fi - -normal_adjacency_count=$(git-rev-list HEAD | check_adjacency | grep -c "\^" | tr -d ' ') -merge_order_adjacency_count=$(git-rev-list --merge-order HEAD | check_adjacency | grep -c "\^" | tr -d ' ') -test_expect_success '--merge-order produces as many or fewer discontinuities' '[ $merge_order_adjacency_count -le $normal_adjacency_count ]' -test_output_expect_success 'simple merge order' 'git-rev-list --merge-order --show-breaks HEAD' <<EOF -= l5 -| l4 -| l3 -= a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -| b1 -^ a3 -| a2 -| a1 -= a0 -| l2 -| l1 -| l0 -= root -EOF - -test_output_expect_success 'two diamonds merge order (g6)' 'git-rev-list --merge-order --show-breaks g4' <<EOF -= g4 -| h2 -^ g3 -= g2 -| h1 -^ g1 -= g0 -EOF - -test_output_expect_success 'multiple heads' 'git-rev-list --merge-order a3 b3 c3' <<EOF -c3 -c2 -c1 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -l1 -l0 -root -EOF - -test_output_expect_success 'multiple heads, prune at a1' 'git-rev-list --merge-order a3 b3 c3 ^a1' <<EOF -c3 -c2 -c1 -b3 -b2 -b1 -a3 -a2 -EOF - -test_output_expect_success 'multiple heads, prune at l1' 'git-rev-list --merge-order a3 b3 c3 ^l1' <<EOF -c3 -c2 -c1 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -EOF - -test_output_expect_success 'cross-epoch, head at l5, prune at l1' 'git-rev-list --merge-order l5 ^l1' <<EOF -l5 -l4 -l3 -a4 -c3 -c2 -c1 -b4 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -EOF - -test_output_expect_success 'duplicated head arguments' 'git-rev-list --merge-order l5 l5 ^l1' <<EOF -l5 -l4 -l3 -a4 -c3 -c2 -c1 -b4 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -EOF - -test_output_expect_success 'prune near merge' 'git-rev-list --merge-order a4 ^c3' <<EOF -a4 -b4 -b3 -a3 -a2 -a1 -EOF - -test_output_expect_success "head has no parent" 'git-rev-list --merge-order --show-breaks root' <<EOF -= root -EOF - -test_output_expect_success "two nodes - one head, one base" 'git-rev-list --merge-order --show-breaks l0' <<EOF -= l0 -= root -EOF - -test_output_expect_success "three nodes one head, one internal, one base" 'git-rev-list --merge-order --show-breaks l1' <<EOF -= l1 -| l0 -= root -EOF - -test_output_expect_success "linear prune l2 ^root" 'git-rev-list --merge-order --show-breaks l2 ^root' <<EOF -^ l2 -| l1 -| l0 -EOF - -test_output_expect_success "linear prune l2 ^l0" 'git-rev-list --merge-order --show-breaks l2 ^l0' <<EOF -^ l2 -| l1 -EOF - -test_output_expect_success "linear prune l2 ^l1" 'git-rev-list --merge-order --show-breaks l2 ^l1' <<EOF -^ l2 -EOF - -test_output_expect_success "linear prune l5 ^a4" 'git-rev-list --merge-order --show-breaks l5 ^a4' <<EOF -^ l5 -| l4 -| l3 -EOF - -test_output_expect_success "linear prune l5 ^l3" 'git-rev-list --merge-order --show-breaks l5 ^l3' <<EOF -^ l5 -| l4 -EOF - -test_output_expect_success "linear prune l5 ^l4" 'git-rev-list --merge-order --show-breaks l5 ^l4' <<EOF -^ l5 -EOF - -test_output_expect_success "max-count 10 - merge order" 'git-rev-list --merge-order --show-breaks --max-count=10 l5' <<EOF -= l5 -| l4 -| l3 -= a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -EOF - -test_output_expect_success "max-count 10 - non merge order" 'git-rev-list --max-count=10 l5' <<EOF -l5 -l4 -l3 -a4 -b4 -a3 -a2 -c3 -c2 -b3 -EOF - -test_output_expect_success '--max-age=c3, no --merge-order' "git-rev-list --max-age=$(commit_date c3) l5" <<EOF -l5 -l4 -l3 -a4 -b4 -a3 -a2 -c3 -EOF - -test_output_expect_success '--max-age=c3, --merge-order' "git-rev-list --merge-order --max-age=$(commit_date c3) l5" <<EOF -l5 -l4 -l3 -a4 -c3 -b4 -a3 -a2 -EOF - -test_output_expect_success 'one specified head reachable from another a4, c3, --merge-order' "list_duplicates git-rev-list --merge-order a4 c3" <<EOF -EOF - -test_output_expect_success 'one specified head reachable from another c3, a4, --merge-order' "list_duplicates git-rev-list --merge-order c3 a4" <<EOF -EOF - -test_output_expect_success 'one specified head reachable from another a4, c3, no --merge-order' "list_duplicates git-rev-list a4 c3" <<EOF -EOF - -test_output_expect_success 'one specified head reachable from another c3, a4, no --merge-order' "list_duplicates git-rev-list c3 a4" <<EOF -EOF - -test_output_expect_success 'graph with c3 and a4 parents of head' "list_duplicates git-rev-list m1" <<EOF -EOF - -test_output_expect_success 'graph with a4 and c3 parents of head' "list_duplicates git-rev-list m2" <<EOF -EOF - -test_expect_success "head ^head --merge-order" 'git-rev-list --merge-order --show-breaks a3 ^a3' <<EOF -EOF - -# -# can't test this now - duplicate parents can't be created -# -#test_output_expect_success 'duplicate parents' 'git-rev-list --parents --merge-order --show-breaks m3' <<EOF -#= m3 c3 a4 c3 -#| a4 c3 b4 a3 -#| b4 a3 b3 -#| b3 b2 -#^ a3 a2 -#| a2 a1 -#| a1 a0 -#^ c3 c2 -#| c2 b2 c1 -#| b2 b1 -#^ c1 b1 -#| b1 a0 -#= a0 l2 -#| l2 l1 -#| l1 l0 -#| l0 root -#= root -#EOF - -test_expect_success "head ^head no --merge-order" 'git-rev-list a3 ^a3' <<EOF -EOF - -test_output_expect_success 'simple merge order (l5r1)' 'git-rev-list --merge-order --show-breaks l5r1' <<EOF -= l5r1 -| r1 -| r0 -| alt_root -^ l5 -| l4 -| l3 -| a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -| b1 -^ a3 -| a2 -| a1 -| a0 -| l2 -| l1 -| l0 -= root -EOF - -test_output_expect_success 'simple merge order (r1l5)' 'git-rev-list --merge-order --show-breaks r1l5' <<EOF -= r1l5 -| l5 -| l4 -| l3 -| a4 -| c3 -| c2 -| c1 -^ b4 -| b3 -| b2 -| b1 -^ a3 -| a2 -| a1 -| a0 -| l2 -| l1 -| l0 -| root -^ r1 -| r0 -= alt_root -EOF - -test_output_expect_success "don't print things unreachable from one branch" "git-rev-list a3 ^b3 --merge-order" <<EOF -a3 -a2 -a1 -EOF - -test_output_expect_success "--merge-order a4 l3" "git-rev-list --merge-order a4 l3" <<EOF -l3 -a4 -c3 -c2 -c1 -b4 -b3 -b2 -b1 -a3 -a2 -a1 -a0 -l2 -l1 -l0 -root -EOF - -# -# - -test_done diff --git a/t/t7001-mv.sh b/t/t7001-mv.sh index 43d74c502e..811a4797a5 100755 --- a/t/t7001-mv.sh +++ b/t/t7001-mv.sh @@ -11,17 +11,31 @@ test_expect_success \ git-commit -m add -a' test_expect_success \ - 'moving the file' \ + 'moving the file out of subdirectory' \ 'cd path0 && git-mv COPYING ../path1/COPYING' # in path0 currently test_expect_success \ 'commiting the change' \ - 'cd .. && git-commit -m move -a' + 'cd .. && git-commit -m move-out -a' test_expect_success \ 'checking the commit' \ 'git-diff-tree -r -M --name-status HEAD^ HEAD | \ grep -E "^R100.+path0/COPYING.+path1/COPYING"' +test_expect_success \ + 'moving the file back into subdirectory' \ + 'cd path0 && git-mv ../path1/COPYING COPYING' + +# in path0 currently +test_expect_success \ + 'commiting the change' \ + 'cd .. && git-commit -m move-in -a' + +test_expect_success \ + 'checking the commit' \ + 'git-diff-tree -r -M --name-status HEAD^ HEAD | \ + grep -E "^R100.+path1/COPYING.+path0/COPYING"' + test_done diff --git a/t/t8001-annotate.sh b/t/t8001-annotate.sh new file mode 100755 index 0000000000..172908a5b0 --- /dev/null +++ b/t/t8001-annotate.sh @@ -0,0 +1,90 @@ +#!/bin/sh + +test_description='git-annotate' +. ./test-lib.sh + +test_expect_success \ + 'prepare reference tree' \ + 'echo "1A quick brown fox jumps over the" >file && + echo "lazy dog" >>file && + git add file + GIT_AUTHOR_NAME="A" git commit -a -m "Initial."' + +test_expect_success \ + 'check all lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]' + +test_expect_success \ + 'Setup new lines blamed on B' \ + 'echo "2A quick brown fox jumps over the" >>file && + echo "lazy dog" >> file && + GIT_AUTHOR_NAME="B" git commit -a -m "Second."' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "A") == 2 ]' + +test_expect_success \ + 'Two lines blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "B") == 2 ]' + +test_expect_success \ + 'merge-setup part 1' \ + 'git checkout -b branch1 master && + echo "3A slow green fox jumps into the" >> file && + echo "well." >> file && + GIT_AUTHOR_NAME="B1" git commit -a -m "Branch1-1"' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]' + +test_expect_success \ + 'Two lines blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 2 ]' + +test_expect_success \ + 'Two lines blamed on B1' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]' + +test_expect_success \ + 'merge-setup part 2' \ + 'git checkout -b branch2 master && + sed -e "s/2A quick brown/4A quick brown lazy dog/" < file > file.new && + mv file.new file && + GIT_AUTHOR_NAME="B2" git commit -a -m "Branch2-1"' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]' + +test_expect_success \ + 'One line blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]' + +test_expect_success \ + 'One line blamed on B2' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]' + + +test_expect_success \ + 'merge-setup part 3' \ + 'git pull . branch1' + +test_expect_success \ + 'Two lines blamed on A' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^A$") == 2 ]' + +test_expect_success \ + 'One line blamed on B' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B$") == 1 ]' + +test_expect_success \ + 'Two lines blamed on B1' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B1$") == 2 ]' + +test_expect_success \ + 'One line blamed on B2' \ + '[ $(git annotate file | awk "{print \$3}" | grep -c "^B2$") == 1 ]' + +test_done diff --git a/tar-tree.c b/tar-tree.c index e85a1ed660..e478e13e28 100644 --- a/tar-tree.c +++ b/tar-tree.c @@ -304,9 +304,11 @@ static void write_header(const unsigned char *sha1, char typeflag, const char *b } if (S_ISDIR(mode)) - mode |= 0755; /* GIT doesn't store permissions of dirs */ - if (S_ISLNK(mode)) - mode |= 0777; /* ... nor of symlinks */ + mode |= 0777; + else if (S_ISREG(mode)) + mode |= (mode & 0100) ? 0777 : 0666; + else if (S_ISLNK(mode)) + mode |= 0777; sprintf(&header[100], "%07o", mode & 07777); /* XXX: should we provide more meaningful info here? */ @@ -391,7 +393,7 @@ int main(int argc, char **argv) usage(tar_tree_usage); } - commit = lookup_commit_reference(sha1); + commit = lookup_commit_reference_gently(sha1, 1); if (commit) { write_global_extended_header(commit->object.sha1); archive_time = commit->date; diff --git a/update-index.c b/update-index.c index ce1db38d16..797245ab27 100644 --- a/update-index.c +++ b/update-index.c @@ -577,9 +577,11 @@ int main(int argc, const char **argv) break; } if (!strcmp(path, "--index-info")) { + if (i != argc - 1) + die("--index-info must be at the end"); allow_add = allow_replace = allow_remove = 1; read_index_info(line_termination); - continue; + break; } if (!strcmp(path, "--ignore-missing")) { not_new = 1; |