Merge branch 'maint'
authorJunio C Hamano <gitster@pobox.com>
Tue, 24 Jun 2008 00:52:02 +0000 (17:52 -0700)
committerJunio C Hamano <gitster@pobox.com>
Tue, 24 Jun 2008 00:52:02 +0000 (17:52 -0700)
* maint:
git-svn: make rebuild respect rewriteRoot option
Workaround for AIX mkstemp()

53 files changed:
Documentation/SubmittingPatches
Documentation/config.txt
Documentation/git-commit.txt
Documentation/git-fast-export.txt
Documentation/git-push.txt
Documentation/git-update-ref.txt
Documentation/gitattributes.txt
Makefile
archive-tar.c
archive-zip.c
archive.c
archive.h
builtin-commit.c
builtin-fast-export.c
builtin-update-ref.c
cache.h
combine-diff.c
config.c
contrib/completion/git-completion.bash
contrib/thunderbird-patch-inline/README [new file with mode: 0644]
contrib/thunderbird-patch-inline/appp.sh [new file with mode: 0755]
diff.c
environment.c
git-compat-util.h
gitweb/gitweb.perl
parse-options.c
path.c
read-cache.c
setup.c
t/.gitattributes
t/README
t/t1400-update-ref.sh
t/t1502-rev-parse-parseopt.sh
t/t3800-mktag.sh
t/t3903-stash.sh
t/t4014-format-patch.sh
t/t4015-diff-whitespace.sh
t/t4016-diff-quote.sh
t/t4109-apply-multifrag.sh
t/t4109/patch1.patch [new file with mode: 0644]
t/t4109/patch2.patch [new file with mode: 0644]
t/t4109/patch3.patch [new file with mode: 0644]
t/t4109/patch4.patch [new file with mode: 0644]
t/t4119-apply-config.sh
t/t4150-am.sh
t/t5000-tar-tree.sh
t/t5540-http-push.sh
t/t7502-status.sh
t/t9301-fast-export.sh
t/test-lib.sh
wrapper.c [new file with mode: 0644]
wt-status.c
wt-status.h
index 0e155c936c25255706bc6d47651c2b336c628417..b1164753e13dbe015d073346bc0b7c992532efe8 100644 (file)
@@ -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
 ----
index 5331b450ea051334d53ce3f1e727e33def2ea2cf..1e09a57c8c5aeb5d49367a4c96f32ebafe07d410 100644 (file)
@@ -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
index 7e8b4ff72c0192f01523118b9a8caeddbcbda09c..d0fe192fb315e62d0daa2d468bd5709601abbe5d 100644 (file)
@@ -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::
index 332346cc5dfd7fffa3e80ad6ca6b413bbbfdb0c2..277a547a026f69faa0d923ca8a9f191608de7294 100644 (file)
@@ -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
 --------
index 89e0049bce1367f308c58d3306266f93a6cd7b75..f3d5d883a7e4e42c47670eeeef41799b1cb0c228 100644 (file)
@@ -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
index 7f7e3d197bafbbb2efe610096b0cad6901488be5..bae2c8b7eced2042af0437b1e92cc73636c7cfa5 100644 (file)
@@ -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
 -----------
index 471754eb12afa5862aa9c5e88ed3bfd2ee5cac7f..6e67990f64372855dd665da4747a7f7c235ac856 100644 (file)
@@ -502,6 +502,12 @@ frotz      unspecified
 Creating an archive
 ~~~~~~~~~~~~~~~~~~~
 
+`export-ignore`
+^^^^^^^^^^^^^^^
+
+Files and directories with the attribute `export-ignore` won't be added to
+archive files.
+
 `export-subst`
 ^^^^^^^^^^^^^^
 
index b003e3e60a1aecae27497d941d211849bedec02b..314339d7e0071ac3e7f761e9c70d1df0c12e7b8c 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -467,6 +467,7 @@ LIB_OBJS += unpack-trees.o
 LIB_OBJS += usage.o
 LIB_OBJS += utf8.o
 LIB_OBJS += walker.o
+LIB_OBJS += wrapper.o
 LIB_OBJS += write_or_die.o
 LIB_OBJS += ws.o
 LIB_OBJS += wt-status.o
@@ -1290,6 +1291,9 @@ endif
 install-doc:
        $(MAKE) -C Documentation install
 
+install-html:
+       $(MAKE) -C Documentation install-html
+
 install-info:
        $(MAKE) -C Documentation install-info
 
index d7598f907d9f7fb40c24c8cef815f8fc33a8b19b..99db58f1cf21ee30f150c54acd6ffefbb14b6f57 100644 (file)
@@ -247,6 +247,8 @@ static int write_tar_entry(const unsigned char *sha1,
        strbuf_grow(&path, PATH_MAX);
        strbuf_add(&path, base, baselen);
        strbuf_addstr(&path, filename);
+       if (is_archive_path_ignored(path.buf + base_len))
+               return 0;
        if (S_ISDIR(mode) || S_ISGITLINK(mode)) {
                strbuf_addch(&path, '/');
                buffer = NULL;
index 18c0f8710c6150e3b0211d311a70bf4b34608370..5742762ac30c92e796255b74d2ed1ea087f3fba0 100644 (file)
@@ -176,6 +176,8 @@ static int write_zip_entry(const unsigned char *sha1,
        crc = crc32(0, NULL, 0);
 
        path = construct_path(base, baselen, filename, S_ISDIR(mode), &pathlen);
+       if (is_archive_path_ignored(path + base_len))
+               return 0;
        if (verbose)
                fprintf(stderr, "%s\n", path);
        if (pathlen > 0xffff) {
index 7a32c19d3ca8043f3ca22dadfdbc60dbbb747d59..6502b76ef10a182e9d9f0e60ad926c55a31a737a 100644 (file)
--- a/archive.c
+++ b/archive.c
@@ -82,3 +82,16 @@ void *sha1_file_to_archive(const char *path, const unsigned char *sha1,
        return buffer;
 }
 
+int is_archive_path_ignored(const char *path)
+{
+       static struct git_attr *attr_export_ignore;
+       struct git_attr_check check[1];
+
+       if (!attr_export_ignore)
+               attr_export_ignore = git_attr("export-ignore", 13);
+
+       check[0].attr = attr_export_ignore;
+       if (git_checkattr(path, ARRAY_SIZE(check), check))
+               return 0;
+       return ATTR_TRUE(check[0].value);
+}
index 5791e657e9a0c22081f4f42b9d8ca5b3c536baf2..ddf004acdfa2125a73df11e923eda902d3ca8277 100644 (file)
--- a/archive.h
+++ b/archive.h
@@ -44,5 +44,6 @@ extern int write_zip_archive(struct archiver_args *);
 extern void *parse_extra_zip_args(int argc, const char **argv);
 
 extern void *sha1_file_to_archive(const char *path, const unsigned char *sha1, unsigned int mode, enum object_type *type, unsigned long *size, const struct commit *commit);
+extern int is_archive_path_ignored(const char *path);
 
 #endif /* ARCHIVE_H */
index 90200ed643bcf21f28a66396f0d15db920a5d4c8..e3ad38b3bd78bc2d19c5e5d1ebfbe0388b22582e 100644 (file)
@@ -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)
index d0a462ff8b3760a4a21d9a72339a02891a5b4aef..45786ef1b740c1e21c1cffeeea89cb0250f446c5 100644 (file)
@@ -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;
 }
index 93c127196d272d99d8af24a5fbc7ac89efdbb3d4..d90d11d2e35c38baab32f05f58125aaf1baee6cc 100644 (file)
@@ -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);
 }
diff --git a/cache.h b/cache.h
index 81b7e17de26ae33249c60b101f5718cb5c5e5699..d12ee7d0f5de3ca5ff64359dcf99d0982ae27263 100644 (file)
--- a/cache.h
+++ b/cache.h
@@ -311,7 +311,6 @@ extern char *git_work_tree_cfg;
 extern int is_inside_work_tree(void);
 extern const char *get_git_dir(void);
 extern char *get_object_directory(void);
-extern char *get_refs_directory(void);
 extern char *get_index_file(void);
 extern char *get_graft_file(void);
 extern int set_git_dir(const char *path);
@@ -525,6 +524,7 @@ static inline int is_absolute_path(const char *path)
 }
 const char *make_absolute_path(const char *path);
 const char *make_nonrelative_path(const char *path);
+const char *make_relative_path(const char *abs, const char *base);
 
 /* Read and unpack a sha1 file into memory, write memory to a sha1 file */
 extern int sha1_object_info(const unsigned char *, unsigned long *);
@@ -735,7 +735,6 @@ extern int git_config_set_multivar(const char *, const char *, const char *, int
 extern int git_config_rename_section(const char *, const char *);
 extern const char *git_etc_gitconfig(void);
 extern int check_repository_format_version(const char *var, const char *value, void *cb);
-extern int git_env_bool(const char *, int);
 extern int git_config_system(void);
 extern int git_config_global(void);
 extern int config_error_nonbool(const char *);
index 588c58bc55dfe78998d40a4dcce2a7b6ea6f7f5a..9f80a1c5e3a461afd11966625589684d61187911 100644 (file)
@@ -84,6 +84,7 @@ struct sline {
        /* bit 0 up to (N-1) are on if the parent has this line (i.e.
         * we did not change it).
         * bit N is used for "interesting" lines, including context.
+        * bit (N+1) is used for "do not show deletion before this".
         */
        unsigned long flag;
        unsigned long *p_lno;
@@ -308,6 +309,7 @@ static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
 {
        unsigned long all_mask = (1UL<<num_parent) - 1;
        unsigned long mark = (1UL<<num_parent);
+       unsigned long no_pre_delete = (2UL<<num_parent);
        unsigned long i;
 
        /* Two groups of interesting lines may have a short gap of
@@ -329,7 +331,7 @@ static int give_context(struct sline *sline, unsigned long cnt, int num_parent)
 
                /* Paint a few lines before the first interesting line. */
                while (j < i)
-                       sline[j++].flag |= mark;
+                       sline[j++].flag |= mark | no_pre_delete;
 
        again:
                /* we know up to i is to be included.  where does the
@@ -502,6 +504,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                       int use_color)
 {
        unsigned long mark = (1UL<<num_parent);
+       unsigned long no_pre_delete = (2UL<<num_parent);
        int i;
        unsigned long lno = 0;
        const char *c_frag = diff_get_color(use_color, DIFF_FRAGINFO);
@@ -581,7 +584,7 @@ static void dump_sline(struct sline *sline, unsigned long cnt, int num_parent,
                        int j;
                        unsigned long p_mask;
                        sl = &sline[lno++];
-                       ll = sl->lost_head;
+                       ll = (sl->flag & no_pre_delete) ? NULL : sl->lost_head;
                        while (ll) {
                                fputs(c_old, stdout);
                                for (j = 0; j < num_parent; j++) {
index c2f2bbb000cd003002c1bd346e6be6c9d3ef5c2d..04d97e3d0b3a202868d3a8645710cc387f14443f 100644 (file)
--- a/config.c
+++ b/config.c
@@ -549,7 +549,7 @@ const char *git_etc_gitconfig(void)
        return system_wide;
 }
 
-int git_env_bool(const char *k, int def)
+static int git_env_bool(const char *k, int def)
 {
        const char *v = getenv(k);
        return v ? git_config_bool(k, v) : def;
index 0eb8df020be3c6dd0e701428a6335316aa608b6a..ebf7cde5c023c86ee7ed0751730e09d1245930da 100755 (executable)
@@ -762,6 +762,9 @@ _git_log ()
                        --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 (file)
index 0000000..39f96aa
--- /dev/null
@@ -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 (executable)
index 0000000..cc518f3
--- /dev/null
@@ -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}"
diff --git a/diff.c b/diff.c
index 526249008662d4041e5623914c1a420f3443c4d4..44e8790690ebdfa45ff7d6c89ede85e8f4f9e857 100644 (file)
--- a/diff.c
+++ b/diff.c
@@ -928,7 +928,8 @@ static void show_stats(struct diffstat_t* data, struct diff_options *options)
                        total = add + del;
                }
                show_name(options->file, prefix, name, len, reset, set);
-               fprintf(options->file, "%5d ", added + deleted);
+               fprintf(options->file, "%5d%s", added + deleted,
+                               added + deleted ? " " : "");
                show_graph(options->file, '+', add, add_c, reset);
                show_graph(options->file, '-', del, del_c, reset);
                fprintf(options->file, "\n");
index 73feb2d03a917d1fbdc8d9397b42fd90e5916bca..187248bf5d027c8d860ccbf839bc6296d0a42436 100644 (file)
@@ -129,13 +129,6 @@ char *get_object_directory(void)
        return git_object_dir;
 }
 
-char *get_refs_directory(void)
-{
-       if (!git_refs_dir)
-               setup_git_env();
-       return git_refs_dir;
-}
-
 char *get_index_file(void)
 {
        if (!git_index_file)
index c04e8baa87f263b426de17556f63351ae254ae95..6f94a8197f479f4e3fd93f8f80bdd125efd8a363 100644 (file)
@@ -240,161 +240,18 @@ static inline char *gitstrchrnul(const char *s, int c)
 
 extern void release_pack_memory(size_t, int);
 
-static inline char* xstrdup(const char *str)
-{
-       char *ret = strdup(str);
-       if (!ret) {
-               release_pack_memory(strlen(str) + 1, -1);
-               ret = strdup(str);
-               if (!ret)
-                       die("Out of memory, strdup failed");
-       }
-       return ret;
-}
-
-static inline void *xmalloc(size_t size)
-{
-       void *ret = malloc(size);
-       if (!ret && !size)
-               ret = malloc(1);
-       if (!ret) {
-               release_pack_memory(size, -1);
-               ret = malloc(size);
-               if (!ret && !size)
-                       ret = malloc(1);
-               if (!ret)
-                       die("Out of memory, malloc failed");
-       }
-#ifdef XMALLOC_POISON
-       memset(ret, 0xA5, size);
-#endif
-       return ret;
-}
-
-/*
- * xmemdupz() allocates (len + 1) bytes of memory, duplicates "len" bytes of
- * "data" to the allocated memory, zero terminates the allocated memory,
- * and returns a pointer to the allocated memory. If the allocation fails,
- * the program dies.
- */
-static inline void *xmemdupz(const void *data, size_t len)
-{
-       char *p = xmalloc(len + 1);
-       memcpy(p, data, len);
-       p[len] = '\0';
-       return p;
-}
-
-static inline char *xstrndup(const char *str, size_t len)
-{
-       char *p = memchr(str, '\0', len);
-       return xmemdupz(str, p ? p - str : len);
-}
-
-static inline void *xrealloc(void *ptr, size_t size)
-{
-       void *ret = realloc(ptr, size);
-       if (!ret && !size)
-               ret = realloc(ptr, 1);
-       if (!ret) {
-               release_pack_memory(size, -1);
-               ret = realloc(ptr, size);
-               if (!ret && !size)
-                       ret = realloc(ptr, 1);
-               if (!ret)
-                       die("Out of memory, realloc failed");
-       }
-       return ret;
-}
-
-static inline void *xcalloc(size_t nmemb, size_t size)
-{
-       void *ret = calloc(nmemb, size);
-       if (!ret && (!nmemb || !size))
-               ret = calloc(1, 1);
-       if (!ret) {
-               release_pack_memory(nmemb * size, -1);
-               ret = calloc(nmemb, size);
-               if (!ret && (!nmemb || !size))
-                       ret = calloc(1, 1);
-               if (!ret)
-                       die("Out of memory, calloc failed");
-       }
-       return ret;
-}
-
-static inline void *xmmap(void *start, size_t length,
-       int prot, int flags, int fd, off_t offset)
-{
-       void *ret = mmap(start, length, prot, flags, fd, offset);
-       if (ret == MAP_FAILED) {
-               if (!length)
-                       return NULL;
-               release_pack_memory(length, fd);
-               ret = mmap(start, length, prot, flags, fd, offset);
-               if (ret == MAP_FAILED)
-                       die("Out of memory? mmap failed: %s", strerror(errno));
-       }
-       return ret;
-}
-
-/*
- * xread() is the same a read(), but it automatically restarts read()
- * operations with a recoverable error (EAGAIN and EINTR). xread()
- * DOES NOT GUARANTEE that "len" bytes is read even if the data is available.
- */
-static inline ssize_t xread(int fd, void *buf, size_t len)
-{
-       ssize_t nr;
-       while (1) {
-               nr = read(fd, buf, len);
-               if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
-                       continue;
-               return nr;
-       }
-}
-
-/*
- * xwrite() is the same a write(), but it automatically restarts write()
- * operations with a recoverable error (EAGAIN and EINTR). xwrite() DOES NOT
- * GUARANTEE that "len" bytes is written even if the operation is successful.
- */
-static inline ssize_t xwrite(int fd, const void *buf, size_t len)
-{
-       ssize_t nr;
-       while (1) {
-               nr = write(fd, buf, len);
-               if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
-                       continue;
-               return nr;
-       }
-}
-
-static inline int xdup(int fd)
-{
-       int ret = dup(fd);
-       if (ret < 0)
-               die("dup failed: %s", strerror(errno));
-       return ret;
-}
-
-static inline FILE *xfdopen(int fd, const char *mode)
-{
-       FILE *stream = fdopen(fd, mode);
-       if (stream == NULL)
-               die("Out of memory? fdopen failed: %s", strerror(errno));
-       return stream;
-}
-
-static inline int xmkstemp(char *template)
-{
-       int fd;
-
-       fd = mkstemp(template);
-       if (fd < 0)
-               die("Unable to create temporary file: %s", strerror(errno));
-       return fd;
-}
+extern char *xstrdup(const char *str);
+extern void *xmalloc(size_t size);
+extern void *xmemdupz(const void *data, size_t len);
+extern char *xstrndup(const char *str, size_t len);
+extern void *xrealloc(void *ptr, size_t size);
+extern void *xcalloc(size_t nmemb, size_t size);
+extern void *xmmap(void *start, size_t length, int prot, int flags, int fd, off_t offset);
+extern ssize_t xread(int fd, void *buf, size_t len);
+extern ssize_t xwrite(int fd, const void *buf, size_t len);
+extern int xdup(int fd);
+extern FILE *xfdopen(int fd, const char *mode);
+extern int xmkstemp(char *template);
 
 static inline size_t xsize_t(off_t len)
 {
index 49b01d8c25b7a7d9c850b5484414bfcfa9bbf85b..87887ab358298565bc14e3c5663f38b7250218fb 100755 (executable)
@@ -539,7 +539,7 @@ sub evaluate_path_info {
 
 # dispatch
 my %actions = (
-       "blame" => \&git_blame2,
+       "blame" => \&git_blame,
        "blobdiff" => \&git_blobdiff,
        "blobdiff_plain" => \&git_blobdiff_plain,
        "blob" => \&git_blob,
@@ -3520,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'}) {
@@ -3546,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);
@@ -3564,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;
@@ -4113,7 +4126,7 @@ sub git_tag {
        git_footer_html();
 }
 
-sub git_blame2 {
+sub git_blame {
        my $fd;
        my $ftype;
 
@@ -4221,103 +4234,6 @@ sub git_blame2 {
        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/ /&nbsp;/g;
-               $age_class  = age_class($age);
-               $author     = esc_html ($author);
-               $author     =~ s/ /&nbsp;/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();
index f8d52e21fe1f0bced3d4abf081f02000bbd7df62..b8bde2b04a14936787392babf5e0ec0a9e3ef1a7 100644 (file)
@@ -312,8 +312,12 @@ void usage_with_options_internal(const char * const *usagestr,
        fprintf(stderr, "usage: %s\n", *usagestr++);
        while (*usagestr && **usagestr)
                fprintf(stderr, "   or: %s\n", *usagestr++);
-       while (*usagestr)
-               fprintf(stderr, "    %s\n", *usagestr++);
+       while (*usagestr) {
+               fprintf(stderr, "%s%s\n",
+                               **usagestr ? "    " : "",
+                               *usagestr);
+               usagestr++;
+       }
 
        if (opts->type != OPTION_GROUP)
                fputc('\n', stderr);
diff --git a/path.c b/path.c
index 7a35a26a1697480f0f43244132d8267deb301651..6e3df1849965be6a88fac89c007f9b5fe960f825 100644 (file)
--- a/path.c
+++ b/path.c
@@ -330,6 +330,23 @@ const char *make_nonrelative_path(const char *path)
 /* We allow "recursive" symbolic links. Only within reason, though. */
 #define MAXDEPTH 5
 
+const char *make_relative_path(const char *abs, const char *base)
+{
+       static char buf[PATH_MAX + 1];
+       int baselen;
+       if (!base)
+               return abs;
+       baselen = strlen(base);
+       if (prefixcmp(abs, base))
+               return abs;
+       if (abs[baselen] == '/')
+               baselen++;
+       else if (base[baselen - 1] != '/')
+               return abs;
+       strcpy(buf, abs + baselen);
+       return buf;
+}
+
 const char *make_absolute_path(const char *path)
 {
        static char bufs[2][PATH_MAX + 1], *buf = bufs[0], *next_buf = bufs[1];
index 8e5fbb619295fe3e4d950e8926d8034d6f825e41..f83de8c4158e08bab3e0445c4d15caf2d4d105aa 100644 (file)
@@ -138,6 +138,16 @@ static int ce_modified_check_fs(struct cache_entry *ce, struct stat *st)
        return 0;
 }
 
+static int is_empty_blob_sha1(const unsigned char *sha1)
+{
+       static const unsigned char empty_blob_sha1[20] = {
+               0xe6,0x9d,0xe2,0x9b,0xb2,0xd1,0xd6,0x43,0x4b,0x8b,
+               0x29,0xae,0x77,0x5a,0xd8,0xc2,0xe4,0x8c,0x53,0x91
+       };
+
+       return !hashcmp(sha1, empty_blob_sha1);
+}
+
 static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
 {
        unsigned int changed = 0;
@@ -193,6 +203,12 @@ static int ce_match_stat_basic(struct cache_entry *ce, struct stat *st)
        if (ce->ce_size != (unsigned int) st->st_size)
                changed |= DATA_CHANGED;
 
+       /* Racily smudged entry? */
+       if (!ce->ce_size) {
+               if (!is_empty_blob_sha1(ce->sha1))
+                       changed |= DATA_CHANGED;
+       }
+
        return changed;
 }
 
diff --git a/setup.c b/setup.c
index d630e374e7c03b934de14adceb93c9c327796e6d..3b111ea7cf5f68ecca085f5a56421ea190e1771a 100644 (file)
--- a/setup.c
+++ b/setup.c
@@ -292,9 +292,10 @@ void setup_work_tree(void)
        work_tree = get_git_work_tree();
        git_dir = get_git_dir();
        if (!is_absolute_path(git_dir))
-               set_git_dir(make_absolute_path(git_dir));
+               git_dir = make_absolute_path(git_dir);
        if (!work_tree || chdir(work_tree))
                die("This operation must be run in a work tree");
+       set_git_dir(make_relative_path(git_dir, work_tree));
        initialized = 1;
 }
 
index ab6edbf19e80a5e07c85ff7cde05061fba349647..1b97c5465b7d9e4404e11668b44c5c507a302d4a 100644 (file)
@@ -1,2 +1 @@
-t[0-9][0-9][0-9][0-9]-*.sh -whitespace
 t[0-9][0-9][0-9][0-9]/* -whitespace
index 70841a4645b2469d8263553a7ba41d4f953774f7..8f12d48fe8b4ffe4a4b37dcd16ce58e50837433f 100644 (file)
--- a/t/README
+++ b/t/README
@@ -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
 ------------
index b8b7ab410354b01584092802365f4b53cd991b4a..f387d46f1a17ac9f9d3319c4da7e30a6c6326c67 100755 (executable)
@@ -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
index 3508d0a612487be824dfe62eaf2377bc05ae8c17..997002d4c40dd8e66e3be5a701e3d99bab1c57c4 100755 (executable)
@@ -5,7 +5,7 @@ test_description='test git rev-parse --parseopt'
 
 cat > expect.err <<EOF
 usage: some-command [options] <args>...
-    
+
     some-command does foo and bar!
 
     -h, --help            show the help
index df1fd6f86f11b40667dfbd8132fef8da45d03d75..c851db8ca9373a890ede7c230710690d5b4b4165 100755 (executable)
@@ -241,11 +241,11 @@ check_verify_failure 'disallow spaces in tag email' \
 ############################################################
 # 17. disallow missing tag timestamp
 
-cat >tag.sig <<EOF
+tr '_' ' ' >tag.sig <<EOF
 object $head
 type commit
 tag mytag
-tagger T A Gger <tagger@example.com>  
+tagger T A Gger <tagger@example.com>__
 
 EOF
 
index 2d3ee3b78c66e4e964fffccaa1ae8252929c1732..54d99ed0c3864e464740833f16833ce6458f731f 100755 (executable)
@@ -41,7 +41,7 @@ test_expect_success 'apply needs clean working directory' '
        echo 4 > other-file &&
        git add other-file &&
        echo 5 > other-file &&
-       test_must_fail git stash apply
+       test_must_fail git stash apply
 '
 
 test_expect_success 'apply stashed changes' '
index 3583e68e92148a5145b82f93ec8a52fd4bb7f200..7fe853c20d1111e40371a3796d82bb8485f5ebcf 100755 (executable)
@@ -98,7 +98,7 @@ test_expect_success 'extra headers' '
        sed -e "/^$/q" patch2 > hdrs2 &&
        grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs2 &&
        grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs2
-       
+
 '
 
 test_expect_success 'extra headers without newlines' '
@@ -109,7 +109,7 @@ test_expect_success 'extra headers without newlines' '
        sed -e "/^$/q" patch3 > hdrs3 &&
        grep "^To: R. E. Cipient <rcipient@example.com>$" hdrs3 &&
        grep "^Cc: S. E. Cipient <scipient@example.com>$" hdrs3
-       
+
 '
 
 test_expect_success 'extra headers with multiple To:s' '
@@ -170,7 +170,7 @@ test_expect_success 'thread cover-letter' '
        git checkout side &&
        git format-patch --cover-letter --thread -o patches/ master &&
        FIRST_MID=$(grep "Message-Id:" patches/0000-* | sed "s/^[^<]*\(<[^>]*>\).*$/\1/") &&
-       for i in patches/0001-* patches/0002-* patches/0003-* 
+       for i in patches/0001-* patches/0002-* patches/0003-*
        do
          grep "References: $FIRST_MID" $i &&
          grep "In-Reply-To: $FIRST_MID" $i || break
index ca0302f41b332df053baa72362099ba691f2ff97..b7cc6b28e61e73af3206a9613620a635a467ea0e 100755 (executable)
@@ -62,16 +62,16 @@ EOF
 
 git update-index x
 
-cat << EOF > x
+tr '_' ' ' << EOF > x
        whitespace at beginning
 whitespace      change
 white space in the middle
-whitespace at end  
+whitespace at end__
 unchanged line
 CR at end
 EOF
 
-tr 'Q' '\015' << EOF > expect
+tr 'Q_' '\015 ' << EOF > expect
 diff --git a/x b/x
 index d99af23..8b32fb5 100644
 --- a/x
@@ -84,7 +84,7 @@ index d99af23..8b32fb5 100644
 +      whitespace at beginning
 +whitespace     change
 +white space in the middle
-+whitespace at end  
++whitespace at end__
  unchanged line
 -CR at endQ
 +CR at end
index 0950250c9be7f7833b7542f5491a4dad8f5ef82e..f07035ab7ec72557be7a0cba9ea286bcbaa79da9 100755 (executable)
@@ -53,13 +53,13 @@ test_expect_success 'git diff --summary -M HEAD' '
 '
 
 cat >expect <<\EOF
- pathname.1 => "Rpathname\twith HT.0"            |    0 
- pathname.3 => "Rpathname\nwith LF.0"            |    0 
- "pathname\twith HT.3" => "Rpathname\nwith LF.1" |    0 
- pathname.2 => Rpathname with SP.0               |    0 
- "pathname\twith HT.2" => Rpathname with SP.1    |    0 
- pathname.0 => Rpathname.0                       |    0 
- "pathname\twith HT.0" => Rpathname.1            |    0 
+ pathname.1 => "Rpathname\twith HT.0"            |    0
+ pathname.3 => "Rpathname\nwith LF.0"            |    0
+ "pathname\twith HT.3" => "Rpathname\nwith LF.1" |    0
+ pathname.2 => Rpathname with SP.0               |    0
+ "pathname\twith HT.2" => Rpathname with SP.1    |    0
+ pathname.0 => Rpathname.0                       |    0
+ "pathname\twith HT.0" => Rpathname.1            |    0
  7 files changed, 0 insertions(+), 0 deletions(-)
 EOF
 test_expect_success 'git diff --stat -M HEAD' '
index bd40a218cd81fdcb4417cb693cfbf047bc0e64c7..ff5fdf35f99e4c50d1bd27eeacd816c37cc5bdfa 100755 (executable)
@@ -9,134 +9,10 @@ test_description='git apply test patches with multiple fragments.
 '
 . ./test-lib.sh
 
-# setup
-
-cat > patch1.patch <<\EOF
-diff --git a/main.c b/main.c
-new file mode 100644
---- /dev/null
-+++ b/main.c
-@@ -0,0 +1,23 @@
-+#include <stdio.h>
-+
-+int func(int num);
-+void print_int(int num);
-+
-+int main() {
-+      int i;
-+
-+      for (i = 0; i < 10; i++) {
-+              print_int(func(i));
-+      }
-+
-+      return 0;
-+}
-+
-+int func(int num) {
-+      return num * num;
-+}
-+
-+void print_int(int num) {
-+      printf("%d", num);
-+}
-+
-EOF
-cat > patch2.patch <<\EOF
-diff --git a/main.c b/main.c
---- a/main.c
-+++ b/main.c
-@@ -1,7 +1,9 @@
-+#include <stdlib.h>
- #include <stdio.h>
- int func(int num);
- void print_int(int num);
-+void print_ln();
- int main() {
-       int i;
-@@ -10,6 +12,8 @@
-               print_int(func(i));
-       }
-+      print_ln();
-+
-       return 0;
- }
-@@ -21,3 +25,7 @@
-       printf("%d", num);
- }
-+void print_ln() {
-+      printf("\n");
-+}
-+
-EOF
-cat > patch3.patch <<\EOF
-diff --git a/main.c b/main.c
---- a/main.c
-+++ b/main.c
-@@ -1,9 +1,7 @@
--#include <stdlib.h>
- #include <stdio.h>
- int func(int num);
- void print_int(int num);
--void print_ln();
- int main() {
-       int i;
-@@ -12,8 +10,6 @@
-               print_int(func(i));
-       }
--      print_ln();
--
-       return 0;
- }
-@@ -25,7 +21,3 @@
-       printf("%d", num);
- }
--void print_ln() {
--      printf("\n");
--}
--
-EOF
-cat > patch4.patch <<\EOF
-diff --git a/main.c b/main.c
---- a/main.c
-+++ b/main.c
-@@ -1,13 +1,14 @@
- #include <stdio.h>
- int func(int num);
--void print_int(int num);
-+int func2(int num);
- int main() {
-       int i;
-       for (i = 0; i < 10; i++) {
--              print_int(func(i));
-+              printf("%d", func(i));
-+              printf("%d", func3(i));
-       }
-       return 0;
-@@ -17,7 +18,7 @@
-       return num * num;
- }
--void print_int(int num) {
--      printf("%d", num);
-+int func2(int num) {
-+      return num * num * num;
- }
-EOF
+cp ../t4109/patch1.patch .
+cp ../t4109/patch2.patch .
+cp ../t4109/patch3.patch .
+cp ../t4109/patch4.patch .
 
 test_expect_success "S = git apply (1)" \
     'git apply patch1.patch patch2.patch'
diff --git a/t/t4109/patch1.patch b/t/t4109/patch1.patch
new file mode 100644 (file)
index 0000000..1d411fc
--- /dev/null
@@ -0,0 +1,28 @@
+diff --git a/main.c b/main.c
+new file mode 100644
+--- /dev/null
++++ b/main.c
+@@ -0,0 +1,23 @@
++#include <stdio.h>
++
++int func(int num);
++void print_int(int num);
++
++int main() {
++      int i;
++
++      for (i = 0; i < 10; i++) {
++              print_int(func(i));
++      }
++
++      return 0;
++}
++
++int func(int num) {
++      return num * num;
++}
++
++void print_int(int num) {
++      printf("%d", num);
++}
++
diff --git a/t/t4109/patch2.patch b/t/t4109/patch2.patch
new file mode 100644 (file)
index 0000000..8c6b06d
--- /dev/null
@@ -0,0 +1,30 @@
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,7 +1,9 @@
++#include <stdlib.h>
+ #include <stdio.h>
+ int func(int num);
+ void print_int(int num);
++void print_ln();
+ int main() {
+       int i;
+@@ -10,6 +12,8 @@
+               print_int(func(i));
+       }
++      print_ln();
++
+       return 0;
+ }
+@@ -21,3 +25,7 @@
+       printf("%d", num);
+ }
++void print_ln() {
++      printf("\n");
++}
++
diff --git a/t/t4109/patch3.patch b/t/t4109/patch3.patch
new file mode 100644 (file)
index 0000000..d696c55
--- /dev/null
@@ -0,0 +1,31 @@
+cat > patch3.patch <<\EOF
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,9 +1,7 @@
+-#include <stdlib.h>
+ #include <stdio.h>
+ int func(int num);
+ void print_int(int num);
+-void print_ln();
+ int main() {
+       int i;
+@@ -12,8 +10,6 @@
+               print_int(func(i));
+       }
+-      print_ln();
+-
+       return 0;
+ }
+@@ -25,7 +21,3 @@
+       printf("%d", num);
+ }
+-void print_ln() {
+-      printf("\n");
+-}
+-
diff --git a/t/t4109/patch4.patch b/t/t4109/patch4.patch
new file mode 100644 (file)
index 0000000..4b08590
--- /dev/null
@@ -0,0 +1,30 @@
+diff --git a/main.c b/main.c
+--- a/main.c
++++ b/main.c
+@@ -1,13 +1,14 @@
+ #include <stdio.h>
+ int func(int num);
+-void print_int(int num);
++int func2(int num);
+ int main() {
+       int i;
+       for (i = 0; i < 10; i++) {
+-              print_int(func(i));
++              printf("%d", func(i));
++              printf("%d", func3(i));
+       }
+       return 0;
+@@ -17,7 +18,7 @@
+       return num * num;
+ }
+-void print_int(int num) {
+-      printf("%d", num);
++int func2(int num) {
++      return num * num * num;
+ }
index b540f7295a1bb48bf044d297201b07aca9fb5005..3c73a783a7e908070308fb1f972f6b5d152e12a4 100755 (executable)
@@ -19,12 +19,12 @@ test_expect_success setup '
 '
 
 # Also handcraft GNU diff output; note this has trailing whitespace.
-cat >gpatch.file <<\EOF &&
+tr '_' ' ' >gpatch.file <<\EOF &&
 --- file1      2007-02-21 01:04:24.000000000 -0800
 +++ file1+     2007-02-21 01:07:44.000000000 -0800
 @@ -1 +1 @@
 -A
-+B 
++B_
 EOF
 
 sed -e 's|file1|sub/&|' gpatch.file >gpatch-sub.file &&
index 722ae96cd5d320e3a8d5771e81d76165b3166b3e..bc982607d09bbdeb0d486eb282f5756a48ae5bcb 100755 (executable)
@@ -110,7 +110,7 @@ test_expect_success 'am applies patch correctly' '
 
 GIT_AUTHOR_NAME="Another Thor"
 GIT_AUTHOR_EMAIL="a.thor@example.com"
-GIT_COMMITTER_NAME="Co M Miter" 
+GIT_COMMITTER_NAME="Co M Miter"
 GIT_COMMITTER_EMAIL="c.miter@example.com"
 export GIT_AUTHOR_NAME GIT_AUTHOR_EMAIL GIT_COMMITTER_NAME GIT_COMMITTER_EMAIL
 
index 9b0baac8db4b342206d37ab5eaff0b7a09d33967..3f1e25d921c2e90176d8487ae0519af2cbd1e17b 100755 (executable)
@@ -44,6 +44,11 @@ test_expect_success \
       echo text >file_with_long_path) &&
      (cd a && find .) | sort >a.lst'
 
+test_expect_success \
+    'add ignored file' \
+    'echo ignore me >a/ignored &&
+     echo ignored export-ignore >.gitattributes'
+
 test_expect_success \
     'add files to repository' \
     'find a -type f | xargs git update-index --add &&
@@ -53,6 +58,10 @@ test_expect_success \
      git update-ref HEAD $(TZ=GMT GIT_COMMITTER_DATE="2005-05-27 22:00:00" \
      git commit-tree $treeid </dev/null)'
 
+test_expect_success \
+    'remove ignored file' \
+    'rm a/ignored'
+
 test_expect_success \
     'git archive' \
     'git archive HEAD >b.tar'
index 7372439164d521df9c29d98b4507612013aba86b..f15dd03e4dbf9febed662f3a285ea1bb35ce396c 100755 (executable)
@@ -38,7 +38,7 @@ test_expect_success 'setup remote repository' '
        cd - &&
        mv test_repo.git $HTTPD_DOCUMENT_ROOT_PATH
 '
-       
+
 test_expect_success 'clone remote repository' '
        cd "$ROOT_PATH" &&
        git clone $HTTPD_URL/test_repo.git test_repo_clone
index 80a438d4d988baa54a25e5725665904c6c45f431..38a48b57c70a888838cfa114be843e1d4aea00d8 100755 (executable)
@@ -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:
index f09bfb1117caa05981d65e45fe2a9cc4c7ca19f3..f1bc5ceef0453fcdf711afd6225a5f0cd5dc69e5 100755 (executable)
@@ -77,6 +77,29 @@ test_expect_success 'iso-8859-1' '
                 git fast-import &&
                 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 &&
+       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
index c861141667eb03eb42f559cbe174c6a76dd704c9..e331cadcff479a8d9de59590c44b0502653653f9 100644 (file)
@@ -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)
diff --git a/wrapper.c b/wrapper.c
new file mode 100644 (file)
index 0000000..4e04f76
--- /dev/null
+++ b/wrapper.c
@@ -0,0 +1,160 @@
+/*
+ * Various trivial helper wrappers around standard functions
+ */
+#include "cache.h"
+
+char *xstrdup(const char *str)
+{
+       char *ret = strdup(str);
+       if (!ret) {
+               release_pack_memory(strlen(str) + 1, -1);
+               ret = strdup(str);
+               if (!ret)
+                       die("Out of memory, strdup failed");
+       }
+       return ret;
+}
+
+void *xmalloc(size_t size)
+{
+       void *ret = malloc(size);
+       if (!ret && !size)
+               ret = malloc(1);
+       if (!ret) {
+               release_pack_memory(size, -1);
+               ret = malloc(size);
+               if (!ret && !size)
+                       ret = malloc(1);
+               if (!ret)
+                       die("Out of memory, malloc failed");
+       }
+#ifdef XMALLOC_POISON
+       memset(ret, 0xA5, size);
+#endif
+       return ret;
+}
+
+/*
+ * xmemdupz() allocates (len + 1) bytes of memory, duplicates "len" bytes of
+ * "data" to the allocated memory, zero terminates the allocated memory,
+ * and returns a pointer to the allocated memory. If the allocation fails,
+ * the program dies.
+ */
+void *xmemdupz(const void *data, size_t len)
+{
+       char *p = xmalloc(len + 1);
+       memcpy(p, data, len);
+       p[len] = '\0';
+       return p;
+}
+
+char *xstrndup(const char *str, size_t len)
+{
+       char *p = memchr(str, '\0', len);
+       return xmemdupz(str, p ? p - str : len);
+}
+
+void *xrealloc(void *ptr, size_t size)
+{
+       void *ret = realloc(ptr, size);
+       if (!ret && !size)
+               ret = realloc(ptr, 1);
+       if (!ret) {
+               release_pack_memory(size, -1);
+               ret = realloc(ptr, size);
+               if (!ret && !size)
+                       ret = realloc(ptr, 1);
+               if (!ret)
+                       die("Out of memory, realloc failed");
+       }
+       return ret;
+}
+
+void *xcalloc(size_t nmemb, size_t size)
+{
+       void *ret = calloc(nmemb, size);
+       if (!ret && (!nmemb || !size))
+               ret = calloc(1, 1);
+       if (!ret) {
+               release_pack_memory(nmemb * size, -1);
+               ret = calloc(nmemb, size);
+               if (!ret && (!nmemb || !size))
+                       ret = calloc(1, 1);
+               if (!ret)
+                       die("Out of memory, calloc failed");
+       }
+       return ret;
+}
+
+void *xmmap(void *start, size_t length,
+       int prot, int flags, int fd, off_t offset)
+{
+       void *ret = mmap(start, length, prot, flags, fd, offset);
+       if (ret == MAP_FAILED) {
+               if (!length)
+                       return NULL;
+               release_pack_memory(length, fd);
+               ret = mmap(start, length, prot, flags, fd, offset);
+               if (ret == MAP_FAILED)
+                       die("Out of memory? mmap failed: %s", strerror(errno));
+       }
+       return ret;
+}
+
+/*
+ * xread() is the same a read(), but it automatically restarts read()
+ * operations with a recoverable error (EAGAIN and EINTR). xread()
+ * DOES NOT GUARANTEE that "len" bytes is read even if the data is available.
+ */
+ssize_t xread(int fd, void *buf, size_t len)
+{
+       ssize_t nr;
+       while (1) {
+               nr = read(fd, buf, len);
+               if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+                       continue;
+               return nr;
+       }
+}
+
+/*
+ * xwrite() is the same a write(), but it automatically restarts write()
+ * operations with a recoverable error (EAGAIN and EINTR). xwrite() DOES NOT
+ * GUARANTEE that "len" bytes is written even if the operation is successful.
+ */
+ssize_t xwrite(int fd, const void *buf, size_t len)
+{
+       ssize_t nr;
+       while (1) {
+               nr = write(fd, buf, len);
+               if ((nr < 0) && (errno == EAGAIN || errno == EINTR))
+                       continue;
+               return nr;
+       }
+}
+
+int xdup(int fd)
+{
+       int ret = dup(fd);
+       if (ret < 0)
+               die("dup failed: %s", strerror(errno));
+       return ret;
+}
+
+FILE *xfdopen(int fd, const char *mode)
+{
+       FILE *stream = fdopen(fd, mode);
+       if (stream == NULL)
+               die("Out of memory? fdopen failed: %s", strerror(errno));
+       return stream;
+}
+
+int xmkstemp(char *template)
+{
+       int fd;
+
+       fd = mkstemp(template);
+       if (fd < 0)
+               die("Unable to create temporary file: %s", strerror(errno));
+       return fd;
+}
index 5b4d74c1f3f4c24aa23cf564a16592d188449da1..28c9e637e3dd42a2c2364dd7b9f099f4671861cb 100644 (file)
@@ -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);
 }
index 597c7ea988634409f920c86008f5ba70910722f9..78add09bd67c727babb61cd1eaa773bcd0c6e55e 100644 (file)
@@ -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;