diff options
52 files changed, 948 insertions, 432 deletions
diff --git a/Documentation/RelNotes-1.5.6.txt b/Documentation/RelNotes-1.5.6.txt index ac4881e19..e143d8d61 100644 --- a/Documentation/RelNotes-1.5.6.txt +++ b/Documentation/RelNotes-1.5.6.txt @@ -18,11 +18,15 @@ Updates since v1.5.5 * "git init" now autodetects the case sensitivity of the filesystem and sets core.ignorecase accordingly. +* cpio is no longer used; neither "curl" binary (libcurl is still used). + (documentation) * Many freestanding documentation pages have been converted and made - available to "git help" (aka "man git-<command>") as section 7 of the - manual pages. + available to "git help" (aka "man git<something>") as section 7 of + the manual pages. This means bookmarks to some HTML documentation + files may need to be updated (eg "tutorial.html" became + "gittutorial.html"). (performance) @@ -108,9 +112,4 @@ Fixes since v1.5.5 All of the fixes in v1.5.5 maintenance series are included in this release, unless otherwise noted. - --- -exec >/var/tmp/1 -O=v1.5.6-rc1 -echo O=`git describe refs/heads/master` -git shortlog --no-merges $O..refs/heads/master ^refs/heads/maint +And there are too numerous small fixes to otherwise note here ;-) diff --git a/Documentation/SubmittingPatches b/Documentation/SubmittingPatches index 0e155c936..b1164753e 100644 --- a/Documentation/SubmittingPatches +++ b/Documentation/SubmittingPatches @@ -419,6 +419,11 @@ settings but I haven't tried, yet. mail.identity.default.compose_html => false mail.identity.id?.compose_html => false +(Lukas Sandström) + +There is a script in contrib/thunderbird-patch-inline which can help +you include patches with Thunderbird in an easy way. To use it, do the +steps above and then use the script as the external editor. Gnus ---- diff --git a/Documentation/config.txt b/Documentation/config.txt index 5331b450e..1e09a57c8 100644 --- a/Documentation/config.txt +++ b/Documentation/config.txt @@ -1013,6 +1013,25 @@ status.relativePaths:: relative to the repository root (this was the default for git prior to v1.5.4). +status.showUntrackedFiles:: + By default, linkgit:git-status[1] and linkgit:git-commit[1] show + files which are not currently tracked by Git. Directories which + contain only untracked files, are shown with the directory name + only. Showing untracked files means that Git needs to lstat() all + all the files in the whole repository, which might be slow on some + systems. So, this variable controls how the commands displays + the untracked files. Possible values are: ++ +-- + - 'no' - Show no untracked files + - 'normal' - Shows untracked files and directories + - 'all' - Shows also individual files in untracked directories. +-- ++ +If this variable is not specified, it defaults to 'normal'. +This variable can be overridden with the -u|--untracked-files option +of linkgit:git-status[1] and linkgit:git-commit[1]. + tar.umask:: This variable can be used to restrict the permission bits of tar archive entries. The default is 0002, which turns off the diff --git a/Documentation/git-add.txt b/Documentation/git-add.txt index 9c6b081e6..b8e3fa675 100644 --- a/Documentation/git-add.txt +++ b/Documentation/git-add.txt @@ -8,8 +8,9 @@ git-add - Add file contents to the index SYNOPSIS -------- [verse] -'git-add' [-n] [-v] [-f] [--interactive | -i] [--patch | -p] [-u] [--refresh] - [--ignore-errors] [--] <filepattern>... +'git-add' [-n] [-v] [--force | -f] [--interactive | -i] [--patch | -p] + [--update | -u] [--refresh] [--ignore-errors] [--] + <filepattern>... DESCRIPTION ----------- @@ -59,6 +60,7 @@ OPTIONS Be verbose. -f:: +--force:: Allow adding otherwise ignored files. -i:: @@ -75,6 +77,7 @@ OPTIONS the specified filepatterns before exiting. -u:: +--update:: Update only files that git already knows about, staging modified content for commit and marking deleted files for removal. This is similar diff --git a/Documentation/git-commit.txt b/Documentation/git-commit.txt index 7e8b4ff72..d0fe192fb 100644 --- a/Documentation/git-commit.txt +++ b/Documentation/git-commit.txt @@ -8,7 +8,7 @@ git-commit - Record changes to the repository SYNOPSIS -------- [verse] -'git-commit' [-a | --interactive] [-s] [-v] [-u] [--amend] +'git-commit' [-a | --interactive] [-s] [-v] [-u<mode>] [--amend] [(-c | -C) <commit>] [-F <file> | -m <msg>] [--allow-empty] [--no-verify] [-e] [--author=<author>] [--cleanup=<mode>] [--] [[-i | -o ]<file>...] @@ -162,13 +162,22 @@ but can be used to amend a merge commit. the last commit without committing changes that have already been staged. --u:: ---untracked-files:: - Show all untracked files, also those in uninteresting - directories, in the "Untracked files:" section of commit - message template. Without this option only its name and - a trailing slash are displayed for each untracked - directory. +-u[<mode>]:: +--untracked-files[=<mode>]:: + Show untracked files (Default: 'all'). ++ +The mode parameter is optional, and is used to specify +the handling of untracked files. The possible options are: ++ +-- + - 'no' - Show no untracked files + - 'normal' - Shows untracked files and directories + - 'all' - Also shows individual files in untracked directories. +-- ++ +See linkgit:git-config[1] for configuration variable +used to change the default for when the option is not +specified. -v:: --verbose:: diff --git a/Documentation/git-fast-export.txt b/Documentation/git-fast-export.txt index 332346cc5..277a547a0 100644 --- a/Documentation/git-fast-export.txt +++ b/Documentation/git-fast-export.txt @@ -36,6 +36,26 @@ when encountering a signed tag. With 'strip', the tags will be made unsigned, with 'verbatim', they will be silently exported and with 'warn', they will be exported, but you will see a warning. +--export-marks=<file>:: + Dumps the internal marks table to <file> when complete. + Marks are written one per line as `:markid SHA-1`. Only marks + for revisions are dumped; marks for blobs are ignored. + Backends can use this file to validate imports after they + have been completed, or to save the marks table across + incremental runs. As <file> is only opened and truncated + at completion, the same path can also be safely given to + \--import-marks. + +--import-marks=<file>:: + Before processing any input, load the marks specified in + <file>. The input file must exist, must be readable, and + must use the same format as produced by \--export-marks. ++ +Any commits that have already been marked will not be exported again. +If the backend uses a similar \--import-marks file, this allows for +incremental bidirectional exporting of the repository by keeping the +marks the same across runs. + EXAMPLES -------- diff --git a/Documentation/git-http-push.txt b/Documentation/git-http-push.txt index 8d32a9204..d69b20549 100644 --- a/Documentation/git-http-push.txt +++ b/Documentation/git-http-push.txt @@ -15,8 +15,8 @@ DESCRIPTION Sends missing objects to remote repository, and updates the remote branch. -*NOTE*: This command is temporarily disabled if your cURL -library is older than 7.16, as the combination has been reported +*NOTE*: This command is temporarily disabled if your libcurl +is older than 7.16, as the combination has been reported not to work and sometimes corrupts repository. OPTIONS diff --git a/Documentation/git-push.txt b/Documentation/git-push.txt index 89e0049bc..f3d5d883a 100644 --- a/Documentation/git-push.txt +++ b/Documentation/git-push.txt @@ -67,7 +67,8 @@ nor in any Push line of the corresponding remotes file---see below). --mirror:: Instead of naming each ref to push, specifies that all - refs under `$GIT_DIR/refs/heads/` and `$GIT_DIR/refs/tags/` + refs under `$GIT_DIR/refs/` (which includes but is not + limited to `refs/heads/`, `refs/remotes/`, and `refs/tags/`) be mirrored to the remote repository. Newly created local refs will be pushed to the remote end, locally updated refs will be force updated on the remote end, and deleted refs diff --git a/Documentation/git-svn.txt b/Documentation/git-svn.txt index f4cbd2f21..97bed54fb 100644 --- a/Documentation/git-svn.txt +++ b/Documentation/git-svn.txt @@ -448,6 +448,8 @@ svn-remote.<name>.rewriteRoot:: the repository with a public http:// or svn:// URL in the metadata so users of it will see the public URL. +-- + Since the noMetadata, rewriteRoot, useSvnsyncProps and useSvmProps options all affect the metadata generated and used by git-svn; they *must* be set in the configuration file before any history is imported @@ -456,7 +458,6 @@ and these settings should never be changed once they are set. Additionally, only one of these four options can be used per-svn-remote section because they affect the 'git-svn-id:' metadata line. --- BASIC EXAMPLES -------------- diff --git a/Documentation/git-update-ref.txt b/Documentation/git-update-ref.txt index 7f7e3d197..bae2c8b7e 100644 --- a/Documentation/git-update-ref.txt +++ b/Documentation/git-update-ref.txt @@ -7,7 +7,7 @@ git-update-ref - Update the object name stored in a ref safely SYNOPSIS -------- -'git-update-ref' [-m <reason>] (-d <ref> <oldvalue> | [--no-deref] <ref> <newvalue> [<oldvalue>]) +'git-update-ref' [-m <reason>] (-d <ref> [<oldvalue>] | [--no-deref] <ref> <newvalue> [<oldvalue>]) DESCRIPTION ----------- diff --git a/Documentation/git.txt b/Documentation/git.txt index ee9677400..7414238fe 100644 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@ -43,6 +43,11 @@ unreleased) version of git, that is available from 'master' branch of the `git.git` repository. Documentation for older releases are available here: +* link:v1.5.6/git.html[documentation for release 1.5.6] + +* release notes for + link:RelNotes-1.5.6.txt[1.5.6], + * link:v1.5.5/git.html[documentation for release 1.5.5] * release notes for diff --git a/Documentation/howto/setup-git-server-over-http.txt b/Documentation/howto/setup-git-server-over-http.txt index b7d09c1ec..403274860 100644 --- a/Documentation/howto/setup-git-server-over-http.txt +++ b/Documentation/howto/setup-git-server-over-http.txt @@ -186,7 +186,7 @@ Step 3: setup the client ------------------------ Make sure that you have HTTP support, i.e. your git was built with -curl (version more recent than 7.10). The command 'git http-push' with +libcurl (version more recent than 7.10). The command 'git http-push' with no argument should display a usage message. Then, add the following to your $HOME/.netrc (you can do without, but will be diff --git a/Documentation/technical/api-path-list.txt b/Documentation/technical/api-path-list.txt index d07768317..9dbedd0a6 100644 --- a/Documentation/technical/api-path-list.txt +++ b/Documentation/technical/api-path-list.txt @@ -1,9 +1,126 @@ path-list API ============= -Talk about <path-list.h>, things like +The path_list API offers a data structure and functions to handle sorted +and unsorted string lists. -* it is not just paths but strings in general; -* the calling sequence. +The name is a bit misleading, a path_list may store not only paths but +strings in general. -(Dscho) +The caller: + +. Allocates and clears a `struct path_list` variable. + +. Initializes the members. You might want to set the flag `strdup_paths` + if the strings should be strdup()ed. For example, this is necessary + when you add something like git_path("..."), since that function returns + a static buffer that will change with the next call to git_path(). ++ +If you need something advanced, you can manually malloc() the `items` +member (you need this if you add things later) and you should set the +`nr` and `alloc` members in that case, too. + +. Adds new items to the list, using `path_list_append` or `path_list_insert`. + +. Can check if a string is in the list using `path_list_has_path` or + `unsorted_path_list_has_path` and get it from the list using + `path_list_lookup` for sorted lists. + +. Can sort an unsorted list using `sort_path_list`. + +. Finally it should free the list using `path_list_clear`. + +Example: + +---- +struct path_list list; +int i; + +memset(&list, 0, sizeof(struct path_list)); +path_list_append("foo", &list); +path_list_append("bar", &list); +for (i = 0; i < list.nr; i++) + printf("%s\n", list.items[i].path) +---- + +NOTE: It is more efficient to build an unsorted list and sort it +afterwards, instead of building a sorted list (`O(n log n)` instead of +`O(n^2)`). ++ +However, if you use the list to check if a certain string was added +already, you should not do that (using unsorted_path_list_has_path()), +because the complexity would be quadratic again (but with a worse factor). + +Functions +--------- + +* General ones (works with sorted and unsorted lists as well) + +`print_path_list`:: + + Dump a path_list to stdout, useful mainly for debugging purposes. It + can take an optional header argument and it writes out the + string-pointer pairs of the path_list, each one in its own line. + +`path_list_clear`:: + + Free a path_list. The `path` pointer of the items will be freed in case + the `strdup_paths` member of the path_list is set. The second parameter + controls if the `util` pointer of the items should be freed or not. + +* Functions for sorted lists only + +`path_list_has_path`:: + + Determine if the path_list has a given string or not. + +`path_list_insert`:: + + Insert a new element to the path_list. The returned pointer can be handy + if you want to write something to the `util` pointer of the + path_list_item containing the just added string. ++ +Since this function uses xrealloc() (which die()s if it fails) if the +list needs to grow, it is safe not to check the pointer. I.e. you may +write `path_list_insert(...)->util = ...;`. + +`path_list_lookup`:: + + Look up a given string in the path_list, returning the containing + path_list_item. If the string is not found, NULL is returned. + +* Functions for unsorted lists only + +`path_list_append`:: + + Append a new string to the end of the path_list. + +`sort_path_list`:: + + Make an unsorted list sorted. + +`unsorted_path_list_has_path`:: + + It's like `path_list_has_path()` but for unsorted lists. ++ +This function needs to look through all items, as opposed to its +counterpart for sorted lists, which performs a binary search. + +Data structures +--------------- + +* `struct path_list_item` + +Represents an item of the list. The `path` member is a pointer to the +string, and you may use the `util` member for any purpose, if you want. + +* `struct path_list` + +Represents the list itself. + +. The array of items are available via the `items` member. +. The `nr` member contains the number of items stored in the list. +. The `alloc` member is used to avoid reallocating at every insertion. + You should not tamper with it. +. Setting the `strdup_paths` member to 1 will strdup() the strings + before adding them, see above. diff --git a/Documentation/technical/api-run-command.txt b/Documentation/technical/api-run-command.txt index c364a22c8..3e1342acf 100644 --- a/Documentation/technical/api-run-command.txt +++ b/Documentation/technical/api-run-command.txt @@ -63,7 +63,7 @@ command to run in a sub-process. The caller: -1. allocates and clears (memset(&chld, '0', sizeof(chld));) a +1. allocates and clears (memset(&chld, 0, sizeof(chld));) a struct child_process variable; 2. initializes the members; 3. calls start_command(); @@ -136,7 +136,7 @@ to produce output that the caller reads. The caller: -1. allocates and clears (memset(&asy, '0', sizeof(asy));) a +1. allocates and clears (memset(&asy, 0, sizeof(asy));) a struct async variable; 2. initializes .proc and .data; 3. calls start_async(); diff --git a/GIT-VERSION-GEN b/GIT-VERSION-GEN index 3cabc92e7..f22144747 100755 --- a/GIT-VERSION-GEN +++ b/GIT-VERSION-GEN @@ -1,7 +1,7 @@ #!/bin/sh GVF=GIT-VERSION-FILE -DEF_VER=v1.5.5.GIT +DEF_VER=v1.5.6.GIT LF=' ' @@ -67,10 +67,10 @@ Issues of note: that come with git (git includes the one from Mozilla, and has 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 - transfer, you are probably OK if you do not have - them. + - libcurl library; git-http-fetch and git-fetch use them. You + might also want the "curl" executable for debugging purposes. + If you do not use http transfer, you are probably OK if you + do not have them. - expat library; git-http-push uses it for remote lock management over DAV. Similar to "curl" above, this is optional. @@ -83,9 +83,6 @@ Issues of note: - "perl" and POSIX-compliant shells are needed to use most of the barebone Porcelainish scripts. - - "cpio" is used by git-clone when doing a local (possibly - hardlinked) clone. - - Some platform specific issues are dealt with Makefile rules, but depending on your specific installation, you may not have all the libraries/tools needed, or you may have @@ -13,7 +13,7 @@ all:: # 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 +# Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. # @@ -1290,6 +1290,9 @@ endif install-doc: $(MAKE) -C Documentation install +install-html: + $(MAKE) -C Documentation install-html + install-info: $(MAKE) -C Documentation install-info @@ -1412,6 +1415,14 @@ check-docs:: documented,gitmodules | \ documented,gitcli | \ documented,git-tools | \ + documented,gitcore-tutorial | \ + documented,gitcvs-migration | \ + documented,gitdiffcore | \ + documented,gitglossary | \ + documented,githooks | \ + documented,gitrepository-layout | \ + documented,gittutorial | \ + documented,gittutorial-2 | \ sentinel,not,matching,is,ok ) continue ;; \ esac; \ case " $(ALL_PROGRAMS) $(BUILT_INS) git gitk " in \ @@ -438,11 +438,13 @@ static void bootstrap_attr_stack(void) elem->prev = attr_stack; attr_stack = elem; - elem = read_attr(GITATTRIBUTES_FILE, 1); - elem->origin = strdup(""); - elem->prev = attr_stack; - attr_stack = elem; - debug_push(elem); + if (!is_bare_repository()) { + elem = read_attr(GITATTRIBUTES_FILE, 1); + elem->origin = strdup(""); + elem->prev = attr_stack; + attr_stack = elem; + debug_push(elem); + } elem = read_attr_from_file(git_path(INFOATTRIBUTES_FILE), 1); if (!elem) @@ -501,22 +503,24 @@ static void prepare_attr_stack(const char *path, int dirlen) /* * Read from parent directories and push them down */ - while (1) { - char *cp; - - len = strlen(attr_stack->origin); - if (dirlen <= len) - break; - memcpy(pathbuf, path, dirlen); - memcpy(pathbuf + dirlen, "/", 2); - cp = strchr(pathbuf + len + 1, '/'); - strcpy(cp + 1, GITATTRIBUTES_FILE); - elem = read_attr(pathbuf, 0); - *cp = '\0'; - elem->origin = strdup(pathbuf); - elem->prev = attr_stack; - attr_stack = elem; - debug_push(elem); + if (!is_bare_repository()) { + while (1) { + char *cp; + + len = strlen(attr_stack->origin); + if (dirlen <= len) + break; + memcpy(pathbuf, path, dirlen); + memcpy(pathbuf + dirlen, "/", 2); + cp = strchr(pathbuf + len + 1, '/'); + strcpy(cp + 1, GITATTRIBUTES_FILE); + elem = read_attr(pathbuf, 0); + *cp = '\0'; + elem->origin = strdup(pathbuf); + elem->prev = attr_stack; + attr_stack = elem; + debug_push(elem); + } } /* diff --git a/builtin-add.c b/builtin-add.c index 1da22eec9..9930cf53f 100644 --- a/builtin-add.c +++ b/builtin-add.c @@ -200,8 +200,8 @@ static struct option builtin_add_options[] = { OPT_GROUP(""), OPT_BOOLEAN('i', "interactive", &add_interactive, "interactive picking"), OPT_BOOLEAN('p', "patch", &patch_interactive, "interactive patching"), - OPT_BOOLEAN('f', NULL, &ignored_too, "allow adding otherwise ignored files"), - OPT_BOOLEAN('u', NULL, &take_worktree_changes, "update tracked files"), + OPT_BOOLEAN('f', "force", &ignored_too, "allow adding otherwise ignored files"), + OPT_BOOLEAN('u', "update", &take_worktree_changes, "update tracked files"), OPT_BOOLEAN( 0 , "refresh", &refresh_only, "don't add, only refresh the index"), OPT_BOOLEAN( 0 , "ignore-errors", &ignore_add_errors, "just skip files which cannot be added because of errors"), OPT_END(), diff --git a/builtin-commit.c b/builtin-commit.c index 90200ed64..e3ad38b3b 100644 --- a/builtin-commit.c +++ b/builtin-commit.c @@ -49,7 +49,8 @@ static char *logfile, *force_author, *template_file; static char *edit_message, *use_message; static char *author_name, *author_email, *author_date; static int all, edit_flag, also, interactive, only, amend, signoff; -static int quiet, verbose, untracked_files, no_verify, allow_empty; +static int quiet, verbose, no_verify, allow_empty; +static char *untracked_files_arg; /* * The default commit message cleanup mode will remove the lines * beginning with # (shell comments) and leading and trailing @@ -102,7 +103,7 @@ static struct option builtin_commit_options[] = { OPT_BOOLEAN('o', "only", &only, "commit only specified files"), OPT_BOOLEAN('n', "no-verify", &no_verify, "bypass pre-commit hook"), OPT_BOOLEAN(0, "amend", &amend, "amend previous commit"), - OPT_BOOLEAN('u', "untracked-files", &untracked_files, "show all untracked files"), + { OPTION_STRING, 'u', "untracked-files", &untracked_files_arg, "mode", "show untracked files, optional modes: all, normal, no. (Default: all)", PARSE_OPT_OPTARG, NULL, (intptr_t)"all" }, OPT_BOOLEAN(0, "allow-empty", &allow_empty, "ok to record an empty change"), OPT_STRING(0, "cleanup", &cleanup_arg, "default", "how to strip spaces and #comments from message"), @@ -347,7 +348,7 @@ static int run_status(FILE *fp, const char *index_file, const char *prefix, int s.reference = "HEAD^1"; } s.verbose = verbose; - s.untracked = untracked_files; + s.untracked = (show_untracked_files == SHOW_ALL_UNTRACKED_FILES); s.index_file = index_file; s.fp = fp; s.nowarn = nowarn; @@ -502,7 +503,8 @@ static int prepare_to_commit(const char *index_file, const char *prefix) fp = fopen(git_path(commit_editmsg), "w"); if (fp == NULL) - die("could not open %s", git_path(commit_editmsg)); + die("could not open %s: %s", + git_path(commit_editmsg), strerror(errno)); if (cleanup_mode != CLEANUP_NONE) stripspace(&sb, 0); @@ -795,6 +797,17 @@ static int parse_and_validate_options(int argc, const char *argv[], else die("Invalid cleanup mode %s", cleanup_arg); + if (!untracked_files_arg) + ; /* default already initialized */ + else if (!strcmp(untracked_files_arg, "no")) + show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "normal")) + show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(untracked_files_arg, "all")) + show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + die("Invalid untracked files mode '%s'", untracked_files_arg); + if (all && argc > 0) die("Paths with -a does not make sense."); else if (interactive && argc > 0) diff --git a/builtin-fast-export.c b/builtin-fast-export.c index d0a462ff8..45786ef1b 100644 --- a/builtin-fast-export.c +++ b/builtin-fast-export.c @@ -56,10 +56,24 @@ static int has_unshown_parent(struct commit *commit) } /* Since intptr_t is C99, we do not use it here */ -static void mark_object(struct object *object) +static inline uint32_t *mark_to_ptr(uint32_t mark) { - last_idnum++; - add_decoration(&idnums, object, ((uint32_t *)NULL) + last_idnum); + return ((uint32_t *)NULL) + mark; +} + +static inline uint32_t ptr_to_mark(void * mark) +{ + return (uint32_t *)mark - (uint32_t *)NULL; +} + +static inline void mark_object(struct object *object, uint32_t mark) +{ + add_decoration(&idnums, object, mark_to_ptr(mark)); +} + +static inline void mark_next_object(struct object *object) +{ + mark_object(object, ++last_idnum); } static int get_object_mark(struct object *object) @@ -67,7 +81,7 @@ static int get_object_mark(struct object *object) void *decoration = lookup_decoration(&idnums, object); if (!decoration) return 0; - return (uint32_t *)decoration - (uint32_t *)NULL; + return ptr_to_mark(decoration); } static void show_progress(void) @@ -100,7 +114,7 @@ static void handle_object(const unsigned char *sha1) if (!buf) die ("Could not read blob %s", sha1_to_hex(sha1)); - mark_object(object); + mark_next_object(object); printf("blob\nmark :%d\ndata %lu\n", last_idnum, size); if (size && fwrite(buf, size, 1, stdout) != 1) @@ -185,7 +199,7 @@ static void handle_commit(struct commit *commit, struct rev_info *rev) for (i = 0; i < diff_queued_diff.nr; i++) handle_object(diff_queued_diff.queue[i]->two->sha1); - mark_object(&commit->object); + mark_next_object(&commit->object); if (!is_encoding_utf8(encoding)) reencoded = reencode_string(message, "UTF-8", encoding); if (!commit->parents) @@ -354,18 +368,85 @@ static void handle_tags_and_duplicates(struct path_list *extra_refs) } } +static void export_marks(char *file) +{ + unsigned int i; + uint32_t mark; + struct object_decoration *deco = idnums.hash; + FILE *f; + + f = fopen(file, "w"); + if (!f) + error("Unable to open marks file %s for writing", file); + + for (i = 0; i < idnums.size; ++i) { + deco++; + if (deco && deco->base && deco->base->type == 1) { + mark = ptr_to_mark(deco->decoration); + fprintf(f, ":%u %s\n", mark, sha1_to_hex(deco->base->sha1)); + } + } + + if (ferror(f) || fclose(f)) + error("Unable to write marks file %s.", file); +} + +static void import_marks(char * input_file) +{ + char line[512]; + FILE *f = fopen(input_file, "r"); + if (!f) + die("cannot read %s: %s", input_file, strerror(errno)); + + while (fgets(line, sizeof(line), f)) { + uint32_t mark; + char *line_end, *mark_end; + unsigned char sha1[20]; + struct object *object; + + line_end = strchr(line, '\n'); + if (line[0] != ':' || !line_end) + die("corrupt mark line: %s", line); + *line_end = 0; + + mark = strtoumax(line + 1, &mark_end, 10); + if (!mark || mark_end == line + 1 + || *mark_end != ' ' || get_sha1(mark_end + 1, sha1)) + die("corrupt mark line: %s", line); + + object = parse_object(sha1); + if (!object) + die ("Could not read blob %s", sha1_to_hex(sha1)); + + if (object->flags & SHOWN) + error("Object %s already has a mark", sha1); + + mark_object(object, mark); + if (last_idnum < mark) + last_idnum = mark; + + object->flags |= SHOWN; + } + fclose(f); +} + int cmd_fast_export(int argc, const char **argv, const char *prefix) { struct rev_info revs; struct object_array commits = { 0, 0, NULL }; struct path_list extra_refs = { NULL, 0, 0, 0 }; struct commit *commit; + char *export_filename = NULL, *import_filename = NULL; struct option options[] = { OPT_INTEGER(0, "progress", &progress, "show progress after <n> objects"), OPT_CALLBACK(0, "signed-tags", &signed_tag_mode, "mode", "select handling of signed tags", parse_opt_signed_tag_mode), + OPT_STRING(0, "export-marks", &export_filename, "FILE", + "Dump marks to this file"), + OPT_STRING(0, "import-marks", &import_filename, "FILE", + "Import marks from this file"), OPT_END() }; @@ -378,6 +459,9 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) if (argc > 1) usage_with_options (fast_export_usage, options); + if (import_filename) + import_marks(import_filename); + get_tags_and_duplicates(&revs.pending, &extra_refs); if (prepare_revision_walk(&revs)) @@ -400,5 +484,8 @@ int cmd_fast_export(int argc, const char **argv, const char *prefix) handle_tags_and_duplicates(&extra_refs); + if (export_filename) + export_marks(export_filename); + return 0; } diff --git a/builtin-reflog.c b/builtin-reflog.c index 897d1dcac..b151e24ff 100644 --- a/builtin-reflog.c +++ b/builtin-reflog.c @@ -307,6 +307,8 @@ static int expire_reflog(const char *ref, const unsigned char *sha1, int unused, unlink(newlog_path); } else if (cmd->updateref && commit_ref(lock)) { status |= error("Couldn't set %s", lock->ref_name); + } else { + adjust_shared_perm(log_file); } } free(newlog_path); diff --git a/builtin-rerere.c b/builtin-rerere.c index 5c811423c..85222d9bc 100644 --- a/builtin-rerere.c +++ b/builtin-rerere.c @@ -43,7 +43,7 @@ static void read_rr(struct path_list *rr) ; /* do nothing */ if (i == sizeof(buf)) die("filename too long"); - path_list_insert(buf, rr)->util = xstrdup(name); + path_list_insert(buf, rr)->util = name; } fclose(in); } diff --git a/builtin-update-ref.c b/builtin-update-ref.c index 93c127196..d90d11d2e 100644 --- a/builtin-update-ref.c +++ b/builtin-update-ref.c @@ -4,14 +4,14 @@ #include "parse-options.h" static const char * const git_update_ref_usage[] = { - "git-update-ref [options] -d <refname> <oldval>", + "git-update-ref [options] -d <refname> [<oldval>]", "git-update-ref [options] <refname> <newval> [<oldval>]", NULL }; int cmd_update_ref(int argc, const char **argv, const char *prefix) { - const char *refname, *value, *oldval, *msg=NULL; + const char *refname, *oldval, *msg=NULL; unsigned char sha1[20], oldsha1[20]; int delete = 0, no_deref = 0; struct option options[] = { @@ -27,25 +27,29 @@ int cmd_update_ref(int argc, const char **argv, const char *prefix) if (msg && !*msg) die("Refusing to perform update with empty message."); - if (argc < 2 || argc > 3) - usage_with_options(git_update_ref_usage, options); - refname = argv[0]; - value = argv[1]; - oldval = argv[2]; - - if (get_sha1(value, sha1)) - die("%s: not a valid SHA1", value); - if (delete) { - if (oldval) + if (argc < 1 || argc > 2) + usage_with_options(git_update_ref_usage, options); + refname = argv[0]; + oldval = argv[1]; + } else { + const char *value; + if (argc < 2 || argc > 3) usage_with_options(git_update_ref_usage, options); - return delete_ref(refname, sha1); + refname = argv[0]; + value = argv[1]; + oldval = argv[2]; + if (get_sha1(value, sha1)) + die("%s: not a valid SHA1", value); } - hashclr(oldsha1); + hashclr(oldsha1); /* all-zero hash in case oldval is the empty string */ if (oldval && *oldval && get_sha1(oldval, oldsha1)) die("%s: not a valid old SHA1", oldval); - return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, - no_deref ? REF_NODEREF : 0, DIE_ON_ERR); + if (delete) + return delete_ref(refname, oldval ? oldsha1 : NULL); + else + return update_ref(msg, refname, sha1, oldval ? oldsha1 : NULL, + no_deref ? REF_NODEREF : 0, DIE_ON_ERR); } @@ -694,8 +694,6 @@ extern struct ref **get_remote_heads(int in, struct ref **list, int nr_match, ch extern int server_supports(const char *feature); extern struct packed_git *parse_pack_index(unsigned char *sha1); -extern struct packed_git *parse_pack_index_file(const unsigned char *sha1, - const char *idx_path); extern void prepare_packed_git(void); extern void reprepare_packed_git(void); diff --git a/configure.ac b/configure.ac index 82584e915..7c2856efc 100644 --- a/configure.ac +++ b/configure.ac @@ -158,7 +158,7 @@ AC_CHECK_LIB([crypto], [SHA1_Init], AC_SUBST(NEEDS_SSL_WITH_CRYPTO) AC_SUBST(NO_OPENSSL) # -# Define NO_CURL if you do not have curl installed. git-http-pull and +# Define NO_CURL if you do not have libcurl installed. git-http-pull and # git-http-push are not built, and you cannot use http:// and https:// # transports. AC_CHECK_LIB([curl], [curl_global_init], diff --git a/contrib/completion/git-completion.bash b/contrib/completion/git-completion.bash index 16984632d..ebf7cde5c 100755 --- a/contrib/completion/git-completion.bash +++ b/contrib/completion/git-completion.bash @@ -500,7 +500,10 @@ _git_add () local cur="${COMP_WORDS[COMP_CWORD]}" case "$cur" in --*) - __gitcomp "--interactive --refresh" + __gitcomp " + --interactive --refresh --patch --update --dry-run + --ignore-errors + " return esac COMPREPLY=() @@ -758,6 +761,10 @@ _git_log () --pretty= --name-status --name-only --raw --not --all --left-right --cherry-pick + --graph + --stat --numstat --shortstat + --decorate --diff-filter= + --color-words --walk-reflogs " return ;; diff --git a/contrib/thunderbird-patch-inline/README b/contrib/thunderbird-patch-inline/README new file mode 100644 index 000000000..39f96aa11 --- /dev/null +++ b/contrib/thunderbird-patch-inline/README @@ -0,0 +1,20 @@ +appp.sh is a script that is supposed to be used together with ExternalEditor +for Mozilla Thundebird. It will let you include patches inline in e-mails +in an easy way. + +Usage: +- Generate the patch with git format-patch. +- Start writing a new e-mail in Thunderbird. +- Press the external editor button (or Ctrl-E) to run appp.sh +- Select the previosly generated patch file. +- Finish editing the e-mail. + +Any text that is entered into the message editor before appp.sh is called +will be moved to the section between the --- and the diffstat. + +All S-O-B:s and Cc:s in the patch will be added to the CC list. + +To set it up, just install External Editor and tell it to use appp.sh as the +editor. + +Zenity is a required dependency. diff --git a/contrib/thunderbird-patch-inline/appp.sh b/contrib/thunderbird-patch-inline/appp.sh new file mode 100755 index 000000000..cc518f3c8 --- /dev/null +++ b/contrib/thunderbird-patch-inline/appp.sh @@ -0,0 +1,55 @@ +#!/bin/bash +# Copyright 2008 Lukas Sandström <luksan@gmail.com> +# +# AppendPatch - A script to be used together with ExternalEditor +# for Mozilla Thunderbird to properly include pathes inline i e-mails. + +# ExternalEditor can be downloaded at http://globs.org/articles.php?lng=en&pg=2 + +CONFFILE=~/.appprc + +SEP="-=-=-=-=-=-=-=-=-=# Don't remove this line #=-=-=-=-=-=-=-=-=-" +if [ -e "$CONFFILE" ] ; then + LAST_DIR=`grep -m 1 "^LAST_DIR=" "${CONFFILE}"|sed -e 's/^LAST_DIR=//'` + cd "${LAST_DIR}" +else + cd > /dev/null +fi + +PATCH=$(zenity --file-selection) + +if [ "$?" != "0" ] ; then + #zenity --error --text "No patchfile given." + exit 1 +fi + +cd - > /dev/null + +SUBJECT=`sed -n -e '/^Subject: /p' "${PATCH}"` +HEADERS=`sed -e '/^'"${SEP}"'$/,$d' $1` +BODY=`sed -e "1,/${SEP}/d" $1` +CMT_MSG=`sed -e '1,/^$/d' -e '/^---$/,$d' "${PATCH}"` +DIFF=`sed -e '1,/^---$/d' "${PATCH}"` + +CCS=`echo -e "$CMT_MSG\n$HEADERS" | sed -n -e 's/^Cc: \(.*\)$/\1,/gp' \ + -e 's/^Signed-off-by: \(.*\)/\1,/gp'` + +echo "$SUBJECT" > $1 +echo "Cc: $CCS" >> $1 +echo "$HEADERS" | sed -e '/^Subject: /d' -e '/^Cc: /d' >> $1 +echo "$SEP" >> $1 + +echo "$CMT_MSG" >> $1 +echo "---" >> $1 +if [ "x${BODY}x" != "xx" ] ; then + echo >> $1 + echo "$BODY" >> $1 + echo >> $1 +fi +echo "$DIFF" >> $1 + +LAST_DIR=`dirname "${PATCH}"` + +grep -v "^LAST_DIR=" "${CONFFILE}" > "${CONFFILE}_" +echo "LAST_DIR=${LAST_DIR}" >> "${CONFFILE}_" +mv "${CONFFILE}_" "${CONFFILE}" @@ -682,10 +682,8 @@ static void date_am(struct tm *tm, int *num) static void date_never(struct tm *tm, int *num) { - tm->tm_mon = tm->tm_wday = tm->tm_yday - = tm->tm_hour = tm->tm_min = tm->tm_sec = 0; - tm->tm_year = 70; - tm->tm_mday = 1; + time_t n = 0; + localtime_r(&n, tm); } static const struct special { @@ -514,9 +514,15 @@ const char *diff_get_color(int diff_use_color, enum color_diff ix) static void emit_line(FILE *file, const char *set, const char *reset, const char *line, int len) { + int has_trailing_newline = (len > 0 && line[len-1] == '\n'); + if (has_trailing_newline) + len--; + fputs(set, file); fwrite(line, len, 1, file); fputs(reset, file); + if (has_trailing_newline) + fputc('\n', file); } static void emit_add_line(const char *reset, struct emit_callback *ecbdata, const char *line, int len) @@ -421,7 +421,7 @@ do else action=yes fi - FIRSTLINE=$(head -1 "$dotest/final-commit") + FIRSTLINE=$(sed 1q "$dotest/final-commit") if test $action = skip then diff --git a/git-merge.sh b/git-merge.sh index 5fc5f5201..8026ccff4 100755 --- a/git-merge.sh +++ b/git-merge.sh @@ -13,7 +13,7 @@ n don't show a diffstat at the end of the merge summary (synonym to --stat) log add list of one-line log to merge commit message squash create a single commit instead of doing a merge -commit perform a commit if the merge sucesses (default) +commit perform a commit if the merge succeeds (default) ff allow fast forward (default) s,strategy= merge strategy to use m,message= message to be used for the merge commit (if any) diff --git a/git-svn.perl b/git-svn.perl index 47b0c37d1..a54979dc5 100755 --- a/git-svn.perl +++ b/git-svn.perl @@ -1023,6 +1023,7 @@ sub get_commit_entry { my $in_msg = 0; my $author; my $saw_from = 0; + my $msgbuf = ""; while (<$msg_fh>) { if (!$in_msg) { $in_msg = 1 if (/^\s*$/); @@ -1035,14 +1036,15 @@ sub get_commit_entry { if (/^From:/ || /^Signed-off-by:/) { $saw_from = 1; } - print $log_fh $_ or croak $!; + $msgbuf .= $_; } } + $msgbuf =~ s/\s+$//s; if ($Git::SVN::_add_author_from && defined($author) && !$saw_from) { - print $log_fh "\nFrom: $author\n" - or croak $!; + $msgbuf .= "\n\nFrom: $author"; } + print $log_fh $msgbuf or croak $!; command_close_pipe($msg_fh, $ctx); } close $log_fh or croak $!; @@ -286,7 +286,7 @@ static void handle_internal_command(int argc, const char **argv) { "checkout-index", cmd_checkout_index, RUN_SETUP | NEED_WORK_TREE}, { "check-ref-format", cmd_check_ref_format }, - { "check-attr", cmd_check_attr, RUN_SETUP | NEED_WORK_TREE }, + { "check-attr", cmd_check_attr, RUN_SETUP }, { "cherry", cmd_cherry, RUN_SETUP }, { "cherry-pick", cmd_cherry_pick, RUN_SETUP | NEED_WORK_TREE }, { "clone", cmd_clone }, diff --git a/git.spec.in b/git.spec.in index 97a26be29..3d7f3ef4a 100644 --- a/git.spec.in +++ b/git.spec.in @@ -12,7 +12,7 @@ BuildRequires: zlib-devel >= 1.2, openssl-devel, curl-devel, expat-devel, gettex BuildRoot: %{_tmppath}/%{name}-%{version}-%{release}-root-%(%{__id_u} -n) Requires: perl-Git = %{version}-%{release} -Requires: zlib >= 1.2, rsync, curl, less, openssh-clients, expat +Requires: zlib >= 1.2, rsync, less, openssh-clients, expat Provides: git-core = %{version}-%{release} Obsoletes: git-core <= 1.5.4.2 Obsoletes: git-p4 @@ -187,6 +187,9 @@ rm -rf $RPM_BUILD_ROOT # No files for you! %changelog +* Sun Jun 15 2008 Junio C Hamano <gitster@pobox.com> +- Remove curl from Requires list. + * Fri Feb 15 2008 Kristian Høgsberg <krh@redhat.com> - Rename git-core to just git and rename meta package from git to git-all. diff --git a/gitweb/gitweb.perl b/gitweb/gitweb.perl index 198772c21..87887ab35 100755 --- a/gitweb/gitweb.perl +++ b/gitweb/gitweb.perl @@ -377,7 +377,7 @@ if (-e $GITWEB_CONFIG) { } # version of the core git binary -our $git_version = qx($GIT --version) =~ m/git version (.*)$/ ? $1 : "unknown"; +our $git_version = qx("$GIT" --version) =~ m/git version (.*)$/ ? $1 : "unknown"; $projects_list ||= $projectroot; @@ -539,7 +539,7 @@ $git_dir = "$projectroot/$project" if $project; # dispatch my %actions = ( - "blame" => \&git_blame2, + "blame" => \&git_blame, "blobdiff" => \&git_blobdiff, "blobdiff_plain" => \&git_blobdiff_plain, "blob" => \&git_blob, @@ -1500,9 +1500,13 @@ sub git_cmd { return $GIT, '--git-dir='.$git_dir; } -# returns path to the core git executable and the --git-dir parameter as string -sub git_cmd_str { - return join(' ', git_cmd()); +# quote the given arguments for passing them to the shell +# quote_command("command", "arg 1", "arg with ' and ! characters") +# => "'command' 'arg 1' 'arg with '\'' and '\!' characters'" +# Try to avoid using this function wherever possible. +sub quote_command { + return join(' ', + map( { my $a = $_; $a =~ s/(['!])/'\\$1'/g; "'$a'" } @_ )); } # get HEAD ref of given project as hash @@ -2158,49 +2162,6 @@ sub parse_commits { return wantarray ? @cos : \@cos; } -# parse ref from ref_file, given by ref_id, with given type -sub parse_ref { - my $ref_file = shift; - my $ref_id = shift; - my $type = shift || git_get_type($ref_id); - my %ref_item; - - $ref_item{'type'} = $type; - $ref_item{'id'} = $ref_id; - $ref_item{'epoch'} = 0; - $ref_item{'age'} = "unknown"; - if ($type eq "tag") { - my %tag = parse_tag($ref_id); - $ref_item{'comment'} = $tag{'comment'}; - if ($tag{'type'} eq "commit") { - my %co = parse_commit($tag{'object'}); - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } elsif (defined($tag{'epoch'})) { - my $age = time - $tag{'epoch'}; - $ref_item{'epoch'} = $tag{'epoch'}; - $ref_item{'age'} = age_string($age); - } - $ref_item{'reftype'} = $tag{'type'}; - $ref_item{'name'} = $tag{'name'}; - $ref_item{'refid'} = $tag{'object'}; - } elsif ($type eq "commit"){ - my %co = parse_commit($ref_id); - $ref_item{'reftype'} = "commit"; - $ref_item{'name'} = $ref_file; - $ref_item{'title'} = $co{'title'}; - $ref_item{'refid'} = $ref_id; - $ref_item{'epoch'} = $co{'committer_epoch'}; - $ref_item{'age'} = $co{'age_string'}; - } else { - $ref_item{'reftype'} = $type; - $ref_item{'name'} = $ref_file; - $ref_item{'refid'} = $ref_id; - } - - return %ref_item; -} - # parse line of git-diff-tree "raw" output sub parse_difftree_raw_line { my $line = shift; @@ -3559,21 +3520,24 @@ sub git_patchset_body { # . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . -sub git_project_list_body { - my ($projlist, $order, $from, $to, $extra, $no_header) = @_; - - my ($check_forks) = gitweb_check_feature('forks'); - +# fills project list info (age, description, owner, forks) for each +# project in the list, removing invalid projects from returned list +# NOTE: modifies $projlist, but does not remove entries from it +sub fill_project_list_info { + my ($projlist, $check_forks) = @_; my @projects; + + PROJECT: foreach my $pr (@$projlist) { - my (@aa) = git_get_last_activity($pr->{'path'}); - unless (@aa) { - next; + my (@activity) = git_get_last_activity($pr->{'path'}); + unless (@activity) { + next PROJECT; } - ($pr->{'age'}, $pr->{'age_string'}) = @aa; + ($pr->{'age'}, $pr->{'age_string'}) = @activity; if (!defined $pr->{'descr'}) { my $descr = git_get_project_description($pr->{'path'}) || ""; - $pr->{'descr_long'} = to_utf8($descr); + $descr = to_utf8($descr); + $pr->{'descr_long'} = $descr; $pr->{'descr'} = chop_str($descr, $projects_list_description_width, 5); } if (!defined $pr->{'owner'}) { @@ -3585,14 +3549,52 @@ sub git_project_list_body { ($pname !~ /\/$/) && (-d "$projectroot/$pname")) { $pr->{'forks'} = "-d $projectroot/$pname"; - } - else { + } else { $pr->{'forks'} = 0; } } push @projects, $pr; } + return @projects; +} + +# print 'sort by' <th> element, either sorting by $key if $name eq $order +# (changing $list), or generating 'sort by $name' replay link otherwise +sub print_sort_th { + my ($str_sort, $name, $order, $key, $header, $list) = @_; + $key ||= $name; + $header ||= ucfirst($name); + + if ($order eq $name) { + if ($str_sort) { + @$list = sort {$a->{$key} cmp $b->{$key}} @$list; + } else { + @$list = sort {$a->{$key} <=> $b->{$key}} @$list; + } + print "<th>$header</th>\n"; + } else { + print "<th>" . + $cgi->a({-href => href(-replay=>1, order=>$name), + -class => "header"}, $header) . + "</th>\n"; + } +} + +sub print_sort_th_str { + print_sort_th(1, @_); +} + +sub print_sort_th_num { + print_sort_th(0, @_); +} + +sub git_project_list_body { + my ($projlist, $order, $from, $to, $extra, $no_header) = @_; + + my ($check_forks) = gitweb_check_feature('forks'); + my @projects = fill_project_list_info($projlist, $check_forks); + $order ||= $default_projects_order; $from = 0 unless defined $from; $to = $#projects if (!defined $to || $#projects < $to); @@ -3603,43 +3605,15 @@ sub git_project_list_body { if ($check_forks) { print "<th></th>\n"; } - if ($order eq "project") { - @projects = sort {$a->{'path'} cmp $b->{'path'}} @projects; - print "<th>Project</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'project'), - -class => "header"}, "Project") . - "</th>\n"; - } - if ($order eq "descr") { - @projects = sort {$a->{'descr'} cmp $b->{'descr'}} @projects; - print "<th>Description</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'descr'), - -class => "header"}, "Description") . - "</th>\n"; - } - if ($order eq "owner") { - @projects = sort {$a->{'owner'} cmp $b->{'owner'}} @projects; - print "<th>Owner</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'owner'), - -class => "header"}, "Owner") . - "</th>\n"; - } - if ($order eq "age") { - @projects = sort {$a->{'age'} <=> $b->{'age'}} @projects; - print "<th>Last Change</th>\n"; - } else { - print "<th>" . - $cgi->a({-href => href(project=>undef, order=>'age'), - -class => "header"}, "Last Change") . - "</th>\n"; - } - print "<th></th>\n" . + print_sort_th_str('project', $order, 'path', + 'Project', \@projects); + print_sort_th_str('descr', $order, 'descr_long', + 'Description', \@projects); + print_sort_th_str('owner', $order, 'owner', + 'Owner', \@projects); + print_sort_th_num('age', $order, 'age', + 'Last Change', \@projects); + print "<th></th>\n" . # for links "</tr>\n"; } my $alternate = 1; @@ -4152,7 +4126,7 @@ sub git_tag { git_footer_html(); } -sub git_blame2 { +sub git_blame { my $fd; my $ftype; @@ -4260,103 +4234,6 @@ HTML git_footer_html(); } -sub git_blame { - my $fd; - - my ($have_blame) = gitweb_check_feature('blame'); - if (!$have_blame) { - die_error('403 Permission denied', "Permission denied"); - } - die_error('404 Not Found', "File name not defined") if (!$file_name); - $hash_base ||= git_get_head_hash($project); - die_error(undef, "Couldn't find base commit") unless ($hash_base); - my %co = parse_commit($hash_base) - or die_error(undef, "Reading commit failed"); - if (!defined $hash) { - $hash = git_get_hash_by_path($hash_base, $file_name, "blob") - or die_error(undef, "Error lookup file"); - } - open ($fd, "-|", git_cmd(), "annotate", '-l', '-t', '-r', $file_name, $hash_base) - or die_error(undef, "Open git-annotate failed"); - git_header_html(); - my $formats_nav = - $cgi->a({-href => href(action=>"blob", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, - "blob") . - " | " . - $cgi->a({-href => href(action=>"history", hash=>$hash, hash_base=>$hash_base, file_name=>$file_name)}, - "history") . - " | " . - $cgi->a({-href => href(action=>"blame", file_name=>$file_name)}, - "HEAD"); - git_print_page_nav('','', $hash_base,$co{'tree'},$hash_base, $formats_nav); - git_print_header_div('commit', esc_html($co{'title'}), $hash_base); - git_print_page_path($file_name, 'blob', $hash_base); - print "<div class=\"page_body\">\n"; - print <<HTML; -<table class="blame"> - <tr> - <th>Commit</th> - <th>Age</th> - <th>Author</th> - <th>Line</th> - <th>Data</th> - </tr> -HTML - my @line_class = (qw(light dark)); - my $line_class_len = scalar (@line_class); - my $line_class_num = $#line_class; - while (my $line = <$fd>) { - my $long_rev; - my $short_rev; - my $author; - my $time; - my $lineno; - my $data; - my $age; - my $age_str; - my $age_class; - - chomp $line; - $line_class_num = ($line_class_num + 1) % $line_class_len; - - if ($line =~ m/^([0-9a-fA-F]{40})\t\(\s*([^\t]+)\t(\d+) [+-]\d\d\d\d\t(\d+)\)(.*)$/) { - $long_rev = $1; - $author = $2; - $time = $3; - $lineno = $4; - $data = $5; - } else { - print qq( <tr><td colspan="5" class="error">Unable to parse: $line</td></tr>\n); - next; - } - $short_rev = substr ($long_rev, 0, 8); - $age = time () - $time; - $age_str = age_string ($age); - $age_str =~ s/ / /g; - $age_class = age_class($age); - $author = esc_html ($author); - $author =~ s/ / /g; - - $data = untabify($data); - $data = esc_html ($data); - - print <<HTML; - <tr class="$line_class[$line_class_num]"> - <td class="sha1"><a href="${\href (action=>"commit", hash=>$long_rev)}" class="text">$short_rev..</a></td> - <td class="$age_class">$age_str</td> - <td>$author</td> - <td class="linenr"><a id="$lineno" href="#$lineno" class="linenr">$lineno</a></td> - <td class="pre">$data</td> - </tr> -HTML - } # while (my $line = <$fd>) - print "</table>\n\n"; - close $fd - or print "Reading blob failed.\n"; - print "</div>"; - git_footer_html(); -} - sub git_tags { my $head = git_get_head_hash($project); git_header_html(); @@ -4633,7 +4510,6 @@ sub git_snapshot { $hash = git_get_head_hash($project); } - my $git_command = git_cmd_str(); my $name = $project; $name =~ s,([^/])/*\.git$,$1,; $name = basename($name); @@ -4641,11 +4517,12 @@ sub git_snapshot { $name =~ s/\047/\047\\\047\047/g; my $cmd; $filename .= "-$hash$known_snapshot_formats{$format}{'suffix'}"; - $cmd = "$git_command archive " . - "--format=$known_snapshot_formats{$format}{'format'} " . - "--prefix=\'$name\'/ $hash"; + $cmd = quote_command( + git_cmd(), 'archive', + "--format=$known_snapshot_formats{$format}{'format'}", + "--prefix=$name/", $hash); if (exists $known_snapshot_formats{$format}{'compressor'}) { - $cmd .= ' | ' . join ' ', @{$known_snapshot_formats{$format}{'compressor'}}; + $cmd .= ' | ' . quote_command(@{$known_snapshot_formats{$format}{'compressor'}}); } print $cgi->header( @@ -4858,8 +4735,8 @@ sub git_object { if ($hash || ($hash_base && !defined $file_name)) { my $object_id = $hash || $hash_base; - my $git_command = git_cmd_str(); - open my $fd, "-|", "$git_command cat-file -t $object_id 2>/dev/null" + open my $fd, "-|", quote_command( + git_cmd(), 'cat-file', '-t', $object_id) . ' 2> /dev/null' or die_error('404 Not Found', "Object does not exist"); $type = <$fd>; chomp $type; @@ -583,7 +583,7 @@ static char *quote_ref_url(const char *base, const char *ref) int len, baselen, ch; baselen = strlen(base); - len = baselen + 7; /* "/refs/" + NUL */ + len = baselen + 2; /* '/' after base and terminating NUL */ for (cp = ref; (ch = *cp) != 0; cp++, len++) if (needs_quote(ch)) len += 2; /* extra two hex plus replacement % */ @@ -867,8 +867,7 @@ static char *guess_ref(const char *name, struct ref *peer) static int match_explicit(struct ref *src, struct ref *dst, struct ref ***dst_tail, - struct refspec *rs, - int errs) + struct refspec *rs) { struct ref *matched_src, *matched_dst; @@ -876,7 +875,7 @@ static int match_explicit(struct ref *src, struct ref *dst, char *dst_guess; if (rs->pattern || rs->matching) - return errs; + return 0; matched_src = matched_dst = NULL; switch (count_refspec_match(rs->src, src, &matched_src)) { @@ -889,23 +888,16 @@ static int match_explicit(struct ref *src, struct ref *dst, */ matched_src = try_explicit_object_name(rs->src); if (!matched_src) - error("src refspec %s does not match any.", rs->src); + return error("src refspec %s does not match any.", rs->src); break; default: - matched_src = NULL; - error("src refspec %s matches more than one.", rs->src); - break; + return error("src refspec %s matches more than one.", rs->src); } - if (!matched_src) - errs = 1; - if (!dst_value) { unsigned char sha1[20]; int flag; - if (!matched_src) - return errs; dst_value = resolve_ref(matched_src->name, sha1, 1, &flag); if (!dst_value || ((flag & REF_ISSYMREF) && @@ -936,18 +928,16 @@ static int match_explicit(struct ref *src, struct ref *dst, dst_value); break; } - if (errs || !matched_dst) - return 1; - if (matched_dst->peer_ref) { - errs = 1; - error("dst ref %s receives from more than one src.", + if (!matched_dst) + return -1; + if (matched_dst->peer_ref) + return error("dst ref %s receives from more than one src.", matched_dst->name); - } else { matched_dst->peer_ref = matched_src; matched_dst->force = rs->force; } - return errs; + return 0; } static int match_explicit_refs(struct ref *src, struct ref *dst, @@ -956,8 +946,8 @@ static int match_explicit_refs(struct ref *src, struct ref *dst, { int i, errs; for (i = errs = 0; i < rs_nr; i++) - errs |= match_explicit(src, dst, dst_tail, &rs[i], errs); - return -errs; + errs += match_explicit(src, dst, dst_tail, &rs[i]); + return errs; } static const struct refspec *check_pattern_match(const struct refspec *rs, diff --git a/sha1_file.c b/sha1_file.c index e27c96ee3..191f814e0 100644 --- a/sha1_file.c +++ b/sha1_file.c @@ -35,8 +35,6 @@ static size_t sz_fmt(size_t s) { return s; } const unsigned char null_sha1[20]; -static unsigned int sha1_file_open_flag = O_NOATIME; - const signed char hexval_table[256] = { -1, -1, -1, -1, -1, -1, -1, -1, /* 00-07 */ -1, -1, -1, -1, -1, -1, -1, -1, /* 08-0f */ @@ -118,7 +116,7 @@ int safe_create_leading_directories(char *path) return 0; } -char * sha1_to_hex(const unsigned char *sha1) +char *sha1_to_hex(const unsigned char *sha1) { static int bufno; static char hexbuffer[4][50]; @@ -399,21 +397,21 @@ void prepare_alt_odb(void) read_info_alternates(get_object_directory(), 0); } -static char *find_sha1_file(const unsigned char *sha1, struct stat *st) +static int has_loose_object(const unsigned char *sha1) { char *name = sha1_file_name(sha1); struct alternate_object_database *alt; - if (!stat(name, st)) - return name; + if (!access(name, F_OK)) + return 1; prepare_alt_odb(); for (alt = alt_odb_list; alt; alt = alt->next) { name = alt->name; fill_sha1_path(name, sha1); - if (!stat(alt->base, st)) - return alt->base; + if (!access(alt->base, F_OK)) + return 1; } - return NULL; + return 0; } static unsigned int pack_used_ctr; @@ -833,13 +831,7 @@ struct packed_git *add_packed_git(const char *path, int path_len, int local) struct packed_git *parse_pack_index(unsigned char *sha1) { - char *path = sha1_pack_index_name(sha1); - return parse_pack_index_file(sha1, path); -} - -struct packed_git *parse_pack_index_file(const unsigned char *sha1, - const char *idx_path) -{ + const char *idx_path = sha1_pack_index_name(sha1); const char *path = sha1_pack_name(sha1); struct packed_git *p = xmalloc(sizeof(*p) + strlen(path) + 2); @@ -997,38 +989,58 @@ int check_sha1_signature(const unsigned char *sha1, void *map, unsigned long siz return hashcmp(sha1, real_sha1) ? -1 : 0; } +static int git_open_noatime(const char *name) +{ + static int sha1_file_open_flag = O_NOATIME; + int fd = open(name, O_RDONLY | sha1_file_open_flag); + + /* Might the failure be due to O_NOATIME? */ + if (fd < 0 && errno != ENOENT && sha1_file_open_flag) { + fd = open(name, O_RDONLY); + if (fd >= 0) + sha1_file_open_flag = 0; + } + return fd; +} + +static int open_sha1_file(const unsigned char *sha1) +{ + int fd; + char *name = sha1_file_name(sha1); + struct alternate_object_database *alt; + + fd = git_open_noatime(name); + if (fd >= 0) + return fd; + + prepare_alt_odb(); + errno = ENOENT; + for (alt = alt_odb_list; alt; alt = alt->next) { + name = alt->name; + fill_sha1_path(name, sha1); + fd = git_open_noatime(alt->base); + if (fd >= 0) + return fd; + } + return -1; +} + static void *map_sha1_file(const unsigned char *sha1, unsigned long *size) { - struct stat st; void *map; int fd; - char *filename = find_sha1_file(sha1, &st); - if (!filename) { - return NULL; - } + fd = open_sha1_file(sha1); + map = NULL; + if (fd >= 0) { + struct stat st; - fd = open(filename, O_RDONLY | sha1_file_open_flag); - if (fd < 0) { - /* See if it works without O_NOATIME */ - switch (sha1_file_open_flag) { - default: - fd = open(filename, O_RDONLY); - if (fd >= 0) - break; - /* Fallthrough */ - case 0: - return NULL; + if (!fstat(fd, &st)) { + *size = xsize_t(st.st_size); + map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); } - - /* If it failed once, it will probably fail again. - * Stop using O_NOATIME - */ - sha1_file_open_flag = 0; + close(fd); } - *size = xsize_t(st.st_size); - map = xmmap(NULL, *size, PROT_READ, MAP_PRIVATE, fd, 0); - close(fd); return map; } @@ -2019,48 +2031,11 @@ static void write_sha1_file_prepare(const void *buf, unsigned long len, } /* - * Link the tempfile to the final place, possibly creating the - * last directory level as you do so. - * - * Returns the errno on failure, 0 on success. - */ -static int link_temp_to_file(const char *tmpfile, const char *filename) -{ - int ret; - char *dir; - - if (!link(tmpfile, filename)) - return 0; - - /* - * Try to mkdir the last path component if that failed. - * - * Re-try the "link()" regardless of whether the mkdir - * succeeds, since a race might mean that somebody - * else succeeded. - */ - ret = errno; - dir = strrchr(filename, '/'); - if (dir) { - *dir = 0; - if (!mkdir(filename, 0777) && adjust_shared_perm(filename)) { - *dir = '/'; - return -2; - } - *dir = '/'; - if (!link(tmpfile, filename)) - return 0; - ret = errno; - } - return ret; -} - -/* * Move the just written object into its final resting place */ int move_temp_to_file(const char *tmpfile, const char *filename) { - int ret = link_temp_to_file(tmpfile, filename); + int ret = link(tmpfile, filename); /* * Coda hack - coda doesn't like cross-directory links, @@ -2114,6 +2089,46 @@ static void close_sha1_file(int fd) die("unable to write sha1 file"); } +/* Size of directory component, including the ending '/' */ +static inline int directory_size(const char *filename) +{ + const char *s = strrchr(filename, '/'); + if (!s) + return 0; + return s - filename + 1; +} + +/* + * This creates a temporary file in the same directory as the final + * 'filename' + * + * We want to avoid cross-directory filename renames, because those + * can have problems on various filesystems (FAT, NFS, Coda). + */ +static int create_tmpfile(char *buffer, size_t bufsiz, const char *filename) +{ + int fd, dirlen = directory_size(filename); + + if (dirlen + 20 > bufsiz) { + errno = ENAMETOOLONG; + return -1; + } + memcpy(buffer, filename, dirlen); + strcpy(buffer + dirlen, "tmp_obj_XXXXXX"); + fd = mkstemp(buffer); + if (fd < 0 && dirlen) { + /* Make sure the directory exists */ + buffer[dirlen-1] = 0; + if (mkdir(buffer, 0777) || adjust_shared_perm(buffer)) + return -1; + + /* Try again */ + strcpy(buffer + dirlen - 1, "/tmp_obj_XXXXXX"); + fd = mkstemp(buffer); + } + return fd; +} + static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, void *buf, unsigned long len, time_t mtime) { @@ -2124,23 +2139,7 @@ static int write_loose_object(const unsigned char *sha1, char *hdr, int hdrlen, static char tmpfile[PATH_MAX]; filename = sha1_file_name(sha1); - fd = open(filename, O_RDONLY); - if (fd >= 0) { - /* - * FIXME!!! We might do collision checking here, but we'd - * need to uncompress the old file and check it. Later. - */ - close(fd); - return 0; - } - - if (errno != ENOENT) { - return error("sha1 file %s: %s\n", filename, strerror(errno)); - } - - snprintf(tmpfile, sizeof(tmpfile), "%s/tmp_obj_XXXXXX", get_object_directory()); - - fd = mkstemp(tmpfile); + fd = create_tmpfile(tmpfile, sizeof(tmpfile), filename); if (fd < 0) { if (errno == EPERM) return error("insufficient permission for adding an object to repository database %s\n", get_object_directory()); @@ -2213,14 +2212,13 @@ int write_sha1_file(void *buf, unsigned long len, const char *type, unsigned cha int force_object_loose(const unsigned char *sha1, time_t mtime) { - struct stat st; void *buf; unsigned long len; enum object_type type; char hdr[32]; int hdrlen; - if (find_sha1_file(sha1, &st)) + if (has_loose_object(sha1)) return 0; buf = read_packed_sha1(sha1, &type, &len); if (!buf) @@ -2253,12 +2251,11 @@ int has_sha1_pack(const unsigned char *sha1, const char **ignore_packed) int has_sha1_file(const unsigned char *sha1) { - struct stat st; struct pack_entry e; if (find_pack_entry(sha1, &e, NULL)) return 1; - return find_sha1_file(sha1, &st) ? 1 : 0; + return has_loose_object(sha1); } int index_pipe(unsigned char *sha1, int fd, const char *type, int write_object) @@ -54,6 +54,38 @@ You can pass --verbose (or -v), --debug (or -d), and --immediate This causes the test to immediately exit upon the first failed test. +--long-tests:: + This causes additional long-running tests to be run (where + available), for more exhaustive testing. + + +Skipping Tests +-------------- + +In some environments, certain tests have no way of succeeding +due to platform limitation, such as lack of 'unzip' program, or +filesystem that do not allow arbitrary sequence of non-NUL bytes +as pathnames. + +You should be able to say something like + + $ GIT_SKIP_TESTS=t9200.8 sh ./t9200-git-cvsexport-commit.sh + +and even: + + $ GIT_SKIP_TESTS='t[0-4]??? t91?? t9200.8' make + +to omit such tests. The value of the environment variable is a +SP separated list of patterns that tells which tests to skip, +and either can match the "t[0-9]{4}" part to skip the whole +test, or t[0-9]{4} followed by ".$number" to say which +particular test to skip. + +Note that some tests in the existing test suite rely on previous +test item, so you cannot arbitrarily disable one and expect the +remainder of test to check what the test originally was intended +to check. + Naming Tests ------------ diff --git a/t/t0003-attributes.sh b/t/t0003-attributes.sh index c56d2fbab..3d8e06a20 100755 --- a/t/t0003-attributes.sh +++ b/t/t0003-attributes.sh @@ -54,4 +54,39 @@ test_expect_success 'root subdir attribute test' ' ' +test_expect_success 'setup bare' ' + + git clone --bare . bare.git && + cd bare.git + +' + +test_expect_success 'bare repository: check that .gitattribute is ignored' ' + + ( + echo "f test=f" + echo "a/i test=a/i" + ) >.gitattributes && + attr_check f unspecified && + attr_check a/f unspecified && + attr_check a/c/f unspecified && + attr_check a/i unspecified && + attr_check subdir/a/i unspecified + +' + +test_expect_success 'bare repository: test info/attributes' ' + + ( + echo "f test=f" + echo "a/i test=a/i" + ) >info/attributes && + attr_check f f && + attr_check a/f f && + attr_check a/c/f f && + attr_check a/i a/i && + attr_check subdir/a/i unspecified + +' + test_done diff --git a/t/t1301-shared-repo.sh b/t/t1301-shared-repo.sh index 5e4252a32..6c78c8bc9 100755 --- a/t/t1301-shared-repo.sh +++ b/t/t1301-shared-repo.sh @@ -83,4 +83,19 @@ do done +test_expect_success 'git reflog expire honors core.sharedRepository' ' + git config core.sharedRepository group && + git reflog expire --all && + actual="$(ls -l .git/logs/refs/heads/master)" && + case "$actual" in + -rw-rw-*) + : happy + ;; + *) + echo Ooops, .git/logs/refs/heads/master is not 0662 [$actual] + false + ;; + esac +' + test_done diff --git a/t/t1400-update-ref.sh b/t/t1400-update-ref.sh index b8b7ab410..f387d46f1 100755 --- a/t/t1400-update-ref.sh +++ b/t/t1400-update-ref.sh @@ -42,6 +42,14 @@ test_expect_success "delete $m" ' ' rm -f .git/$m +test_expect_success "delete $m without oldvalue verification" " + git update-ref $m $A && + test $A = \$(cat .git/$m) && + git update-ref -d $m && + ! test -f .git/$m +" +rm -f .git/$m + test_expect_success \ "fail to create $n" \ "touch .git/$n_dir diff --git a/t/t5510-fetch.sh b/t/t5510-fetch.sh index 6946557c6..df7750f7d 100755 --- a/t/t5510-fetch.sh +++ b/t/t5510-fetch.sh @@ -37,7 +37,8 @@ test_expect_success "clone and setup child repos" ' echo "Pull: refs/heads/one:refs/heads/one" } >.git/remotes/two && cd .. && - git clone . bundle + git clone . bundle && + git clone . seven ' test_expect_success "fetch test" ' @@ -295,4 +296,11 @@ test_expect_success 'configured fetch updates tracking' ' ) ' +test_expect_success 'pushing nonexistent branch by mistake should not segv' ' + + cd "$D" && + test_must_fail git push seven no:no + +' + test_done diff --git a/t/t7502-commit.sh b/t/t7502-commit.sh index ed871a6b4..c25eff9e4 100755 --- a/t/t7502-commit.sh +++ b/t/t7502-commit.sh @@ -212,7 +212,11 @@ test_expect_success 'do not fire editor in the presence of conflicts' ' # Must fail due to conflict test_must_fail git cherry-pick -n master && echo "editor not started" >.git/result && - test_must_fail GIT_EDITOR="$(pwd)/.git/FAKE_EDITOR" git commit && + ( + GIT_EDITOR="$(pwd)/.git/FAKE_EDITOR" && + export GIT_EDITOR && + test_must_fail git commit + ) && test "$(cat .git/result)" = "editor not started" ' diff --git a/t/t7502-status.sh b/t/t7502-status.sh index 80a438d4d..38a48b57c 100755 --- a/t/t7502-status.sh +++ b/t/t7502-status.sh @@ -67,6 +67,104 @@ test_expect_success 'status (2)' ' ' +cat >expect <<EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# new file: dir2/added +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Untracked files not listed (use -u option to show untracked files) +EOF +test_expect_success 'status -uno' ' + mkdir dir3 && + : > dir3/untracked1 && + : > dir3/untracked2 && + git status -uno >output && + test_cmp expect output +' + +test_expect_success 'status (status.showUntrackedFiles no)' ' + git config status.showuntrackedfiles no + git status >output && + test_cmp expect output +' + +cat >expect <<EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# new file: dir2/added +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# dir3/ +# expect +# output +# untracked +EOF +test_expect_success 'status -unormal' ' + git status -unormal >output && + test_cmp expect output +' + +test_expect_success 'status (status.showUntrackedFiles normal)' ' + git config status.showuntrackedfiles normal + git status >output && + test_cmp expect output +' + +cat >expect <<EOF +# On branch master +# Changes to be committed: +# (use "git reset HEAD <file>..." to unstage) +# +# new file: dir2/added +# +# Changed but not updated: +# (use "git add <file>..." to update what will be committed) +# +# modified: dir1/modified +# +# Untracked files: +# (use "git add <file>..." to include in what will be committed) +# +# dir1/untracked +# dir2/modified +# dir2/untracked +# dir3/untracked1 +# dir3/untracked2 +# expect +# output +# untracked +EOF +test_expect_success 'status -uall' ' + git status -uall >output && + test_cmp expect output +' +test_expect_success 'status (status.showUntrackedFiles all)' ' + git config status.showuntrackedfiles all + git status >output && + rm -rf dir3 && + git config --unset status.showuntrackedfiles && + test_cmp expect output +' + cat > expect << \EOF # On branch master # Changes to be committed: diff --git a/t/t9122-git-svn-author.sh b/t/t9122-git-svn-author.sh index 8c58f0b8d..1190576a6 100755 --- a/t/t9122-git-svn-author.sh +++ b/t/t9122-git-svn-author.sh @@ -64,7 +64,21 @@ test_expect_success 'interact with it via git-svn' ' # Make sure --add-author-from with --use-log-author affected # the authorship information - grep "^Author: A U Thor " actual.4 + grep "^Author: A U Thor " actual.4 && + + # Make sure there are no commit messages with excess blank lines + test $(grep "^ " actual.2 | wc -l) = 3 && + test $(grep "^ " actual.3 | wc -l) = 5 && + test $(grep "^ " actual.4 | wc -l) = 5 && + + # Make sure there are no svn commit messages with excess blank lines + ( + cd work.svn && + svn up && + + test $(svn log -r2:2 | wc -l) = 5 && + test $(svn log -r4:4 | wc -l) = 7 + ) ' test_done diff --git a/t/t9301-fast-export.sh b/t/t9301-fast-export.sh index f09bfb111..60b5ee397 100755 --- a/t/t9301-fast-export.sh +++ b/t/t9301-fast-export.sh @@ -78,6 +78,30 @@ test_expect_success 'iso-8859-1' ' git cat-file commit i18n | grep "Áéí óú") ' +test_expect_success 'import/export-marks' ' + + git checkout -b marks master && + git fast-export --export-marks=tmp-marks HEAD && + test -s tmp-marks && + cp tmp-marks ~ && + test $(wc -l < tmp-marks) -eq 3 && + test $( + git fast-export --import-marks=tmp-marks\ + --export-marks=tmp-marks HEAD | + grep ^commit | + wc -l) \ + -eq 0 && + echo change > file && + git commit -m "last commit" file && + test $( + git fast-export --import-marks=tmp-marks \ + --export-marks=tmp-marks HEAD | + grep ^commit\ | + wc -l) \ + -eq 1 && + test $(wc -l < tmp-marks) -eq 4 + +' cat > signed-tag-import << EOF tag sign-your-name diff --git a/t/test-lib.sh b/t/test-lib.sh index e9c9081ed..e331cadcf 100644 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@ -80,6 +80,8 @@ do debug=t; shift ;; -i|--i|--im|--imm|--imme|--immed|--immedi|--immedia|--immediat|--immediate) immediate=t; shift ;; + -l|--l|--lo|--lon|--long|--long-|--long-t|--long-te|--long-tes|--long-test|--long-tests) + export GIT_TEST_LONG=t; shift ;; -h|--h|--he|--hel|--help) help=t; shift ;; -v|--v|--ve|--ver|--verb|--verbo|--verbos|--verbose) @@ -386,7 +388,7 @@ test_done () { } # Test the binaries we have just built. The tests are kept in -# t/ subdirectory and are run in trash subdirectory. +# t/ subdirectory and are run in 'trash directory' subdirectory. PATH=$(pwd)/..:$PATH GIT_EXEC_PATH=$(pwd)/.. GIT_TEMPLATE_DIR=$(pwd)/../templates/blt diff --git a/wt-status.c b/wt-status.c index 5b4d74c1f..28c9e637e 100644 --- a/wt-status.c +++ b/wt-status.c @@ -27,6 +27,7 @@ static const char use_add_rm_msg[] = "use \"git add/rm <file>...\" to update what will be committed"; static const char use_add_to_include_msg[] = "use \"git add <file>...\" to include in what will be committed"; +enum untracked_status_type show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; static int parse_status_slot(const char *var, int offset) { @@ -347,7 +348,10 @@ void wt_status_print(struct wt_status *s) wt_status_print_changed(s); if (wt_status_submodule_summary) wt_status_print_submodule_summary(s); - wt_status_print_untracked(s); + if (show_untracked_files) + wt_status_print_untracked(s); + else if (s->commitable) + fprintf(s->fp, "# Untracked files not listed (use -u option to show untracked files)\n"); if (s->verbose && !s->is_initial) wt_status_print_verbose(s); @@ -362,6 +366,8 @@ void wt_status_print(struct wt_status *s) printf("nothing added to commit but untracked files present (use \"git add\" to track)\n"); else if (s->is_initial) printf("nothing to commit (create/copy files and use \"git add\" to track)\n"); + else if (!show_untracked_files) + printf("nothing to commit (use -u to show untracked files)\n"); else printf("nothing to commit (working directory clean)\n"); } @@ -391,5 +397,18 @@ int git_status_config(const char *k, const char *v, void *cb) wt_status_relative_paths = git_config_bool(k, v); return 0; } + if (!strcmp(k, "status.showuntrackedfiles")) { + if (!v) + return config_error_nonbool(v); + else if (!strcmp(v, "no")) + show_untracked_files = SHOW_NO_UNTRACKED_FILES; + else if (!strcmp(v, "normal")) + show_untracked_files = SHOW_NORMAL_UNTRACKED_FILES; + else if (!strcmp(v, "all")) + show_untracked_files = SHOW_ALL_UNTRACKED_FILES; + else + return error("Invalid untracked files mode '%s'", v); + return 0; + } return git_color_default_config(k, v, cb); } diff --git a/wt-status.h b/wt-status.h index 597c7ea98..78add09bd 100644 --- a/wt-status.h +++ b/wt-status.h @@ -11,6 +11,13 @@ enum color_wt_status { WT_STATUS_NOBRANCH, }; +enum untracked_status_type { + SHOW_NO_UNTRACKED_FILES, + SHOW_NORMAL_UNTRACKED_FILES, + SHOW_ALL_UNTRACKED_FILES +}; +extern enum untracked_status_type show_untracked_files; + struct wt_status { int is_initial; char *branch; |