From: Junio C Hamano Date: Wed, 17 Dec 2014 19:46:57 +0000 (-0800) Subject: Sync with v2.1.4 X-Git-Tag: v2.2.1~1 X-Git-Url: https://git.lorimer.id.au/gitweb.git/diff_plain/77933f4449b8d6aa7529d627f3c7b55336f491db?ds=inline;hp=-c Sync with v2.1.4 * maint-2.1: Git 2.1.4 Git 2.0.5 Git 1.9.5 Git 1.8.5.6 fsck: complain about NTFS ".git" aliases in trees read-cache: optionally disallow NTFS .git variants path: add is_ntfs_dotgit() helper fsck: complain about HFS+ ".git" aliases in trees read-cache: optionally disallow HFS+ .git variants utf8: add is_hfs_dotgit() helper fsck: notice .git case-insensitively t1450: refactor ".", "..", and ".git" fsck tests verify_dotfile(): reject .git case-insensitively read-tree: add tests for confusing paths like ".." and ".git" unpack-trees: propagate errors adding entries to the index --- 77933f4449b8d6aa7529d627f3c7b55336f491db diff --combined Documentation/config.txt index 922072596f,0a4d22aaf7..302d61e76f --- a/Documentation/config.txt +++ b/Documentation/config.txt @@@ -204,26 -204,13 +204,26 @@@ advice.*: -- core.fileMode:: - If false, the executable bit differences between the index and - the working tree are ignored; useful on broken filesystems like FAT. - See linkgit:git-update-index[1]. + Tells Git if the executable bit of files in the working tree + is to be honored. + -The default is true, except linkgit:git-clone[1] or linkgit:git-init[1] -will probe and set core.fileMode false if appropriate when the -repository is created. +Some filesystems lose the executable bit when a file that is +marked as executable is checked out, or checks out an +non-executable file with executable bit on. +linkgit:git-clone[1] or linkgit:git-init[1] probe the filesystem +to see if it handles the executable bit correctly +and this variable is automatically set as necessary. ++ +A repository, however, may be on a filesystem that handles +the filemode correctly, and this variable is set to 'true' +when created, but later may be made accessible from another +environment that loses the filemode (e.g. exporting ext4 via +CIFS mount, visiting a Cygwin created repository with +Git for Windows or Eclipse). +In such a case it may be necessary to set this variable to 'false'. +See linkgit:git-update-index[1]. ++ +The default is true (when core.filemode is not specified in the config file). core.ignorecase:: If true, this option enables various workarounds to enable @@@ -246,6 -233,17 +246,17 @@@ core.precomposeunicode: When false, file names are handled fully transparent by Git, which is backward compatible with older versions of Git. + core.protectHFS:: + If set to true, do not allow checkout of paths that would + be considered equivalent to `.git` on an HFS+ filesystem. + Defaults to `true` on Mac OS, and `false` elsewhere. + + core.protectNTFS:: + If set to true, do not allow checkout of paths that would + cause problems with the NTFS filesystem, e.g. conflict with + 8.3 "short" names. + Defaults to `true` on Windows, and `false` elsewhere. + core.trustctime:: If false, the ctime differences between the index and the working tree are ignored; useful when the inode change time @@@ -512,8 -510,7 +523,8 @@@ core.bigFileThreshold: Files larger than this size are stored deflated, without attempting delta compression. Storing large files without delta compression avoids excessive memory usage, at the - slight expense of increased disk usage. + slight expense of increased disk usage. Additionally files + larger than this size are always treated as binary. + Default is 512 MiB on all platforms. This should be reasonable for most projects as source code and other text files can still @@@ -885,11 -882,7 +896,11 @@@ color.grep.: `linenumber`;; line number prefix (when using `-n`) `match`;; - matching text + matching text (same as setting `matchContext` and `matchSelected`) +`matchContext`;; + matching text in context lines +`matchSelected`;; + matching text in selected lines `selected`;; non-matching text in selected lines `separator`;; @@@ -1210,7 -1203,7 +1221,7 @@@ gc.autopacklimit: default value is 50. Setting this to 0 disables it. gc.autodetach:: - Make `git gc --auto` return immediately andrun in background + Make `git gc --auto` return immediately and run in background if the system supports it. Default is true. gc.packrefs:: @@@ -1357,7 -1350,7 +1368,7 @@@ gpg.program: same command-line interface as GPG, namely, to verify a detached signature, "gpg --verify $file - <$signature" is run, and the program is expected to signal a good signature by exiting with - code 0, and to generate an ascii-armored detached signature, the + code 0, and to generate an ASCII-armored detached signature, the standard input of "gpg -bsau $key" is fed with the contents to be signed, and the program is expected to send the result to its standard output. @@@ -1592,7 -1585,7 +1603,7 @@@ http.useragent: Can be overridden by the 'GIT_HTTP_USER_AGENT' environment variable. http..*:: - Any of the http.* options above can be applied selectively to some urls. + Any of the http.* options above can be applied selectively to some URLs. For a config key to match a URL, each element of the config key is compared to that of the URL, in the following order: + @@@ -1631,8 -1624,8 +1642,8 @@@ if the URL is `https://user@example.com + All URLs are normalized before attempting any matching (the password part, if embedded in the URL, is always ignored for matching purposes) so that -equivalent urls that are simply spelled differently will match properly. -Environment variable settings always override any matches. The urls that are +equivalent URLs that are simply spelled differently will match properly. +Environment variable settings always override any matches. The URLs that are matched against are those given directly to Git commands. This means any URLs visited as a result of a redirection do not participate in matching. @@@ -1794,12 -1787,6 +1805,12 @@@ mergetool.keepTemporaries: preserved, otherwise they will be removed after the tool has exited. Defaults to `false`. +mergetool.writeToTemp:: + Git writes temporary 'BASE', 'LOCAL', and 'REMOTE' versions of + conflicting files in the worktree by default. Git will attempt + to use a temporary directory for these files when set `true`. + Defaults to `false`. + mergetool.prompt:: Prompt before each invocation of the merge resolution program. @@@ -1860,11 -1847,10 +1871,11 @@@ pack.depth: maximum depth is given on the command line. Defaults to 50. pack.windowMemory:: - The window memory size limit used by linkgit:git-pack-objects[1] - when no limit is given on the command line. The value can be - suffixed with "k", "m", or "g". Defaults to 0, meaning no - limit. + The maximum size of memory that is consumed by each thread + in linkgit:git-pack-objects[1] for pack window memory when + no limit is given on the command line. The value can be + suffixed with "k", "m", or "g". When left unconfigured (or + set explicitly to 0), there will be no limit. pack.compression:: An integer -1..9, indicating the compression level for objects @@@ -2077,25 -2063,6 +2088,25 @@@ receive.autogc: receiving data from git-push and updating refs. You can stop it by setting this variable to false. +receive.certnonceseed:: + By setting this variable to a string, `git receive-pack` + will accept a `git push --signed` and verifies it by using + a "nonce" protected by HMAC using this string as a secret + key. + +receive.certnonceslop:: + When a `git push --signed` sent a push certificate with a + "nonce" that was issued by a receive-pack serving the same + repository within this many seconds, export the "nonce" + found in the certificate to `GIT_PUSH_CERT_NONCE` to the + hooks (instead of what the receive-pack asked the sending + side to include). This may allow writing checks in + `pre-receive` and `post-receive` a bit easier. Instead of + checking `GIT_PUSH_CERT_NONCE_SLOP` environment variable + that records by how many seconds the nonce is stale to + decide if they want to accept the certificate, they only + can check `GIT_PUSH_CERT_NONCE_STATUS` is `OK`. + receive.fsckObjects:: If it is set to true, git-receive-pack will check all received objects. It will abort in the case of a malformed object or a diff --combined Documentation/git.txt index afb48d39bb,78cc101b03..4360a79544 --- a/Documentation/git.txt +++ b/Documentation/git.txt @@@ -22,7 -22,7 +22,7 @@@ unusually rich command set that provide and full access to internals. See linkgit:gittutorial[7] to get started, then see -link:everyday.html[Everyday Git] for a useful minimum set of +linkgit:giteveryday[7] for a useful minimum set of commands. The link:user-manual.html[Git User's Manual] has a more in-depth introduction. @@@ -43,40 -43,39 +43,44 @@@ unreleased) version of Git, that is ava branch of the `git.git` repository. Documentation for older releases are available here: +* link:v2.2.0/git.html[documentation for release 2.2] + +* release notes for + link:RelNotes/2.2.0.txt[2.2]. + - * link:v2.1.3/git.html[documentation for release 2.1.3] + * link:v2.1.4/git.html[documentation for release 2.1.4] * release notes for + link:RelNotes/2.1.4.txt[2.1.4], link:RelNotes/2.1.3.txt[2.1.3], link:RelNotes/2.1.2.txt[2.1.2], link:RelNotes/2.1.1.txt[2.1.1], link:RelNotes/2.1.0.txt[2.1]. - * link:v2.0.4/git.html[documentation for release 2.0.4] + * link:v2.0.5/git.html[documentation for release 2.0.5] * release notes for + link:RelNotes/2.0.5.txt[2.0.5], link:RelNotes/2.0.4.txt[2.0.4], link:RelNotes/2.0.3.txt[2.0.3], link:RelNotes/2.0.2.txt[2.0.2], link:RelNotes/2.0.1.txt[2.0.1], link:RelNotes/2.0.0.txt[2.0.0]. - * link:v1.9.4/git.html[documentation for release 1.9.4] + * link:v1.9.5/git.html[documentation for release 1.9.5] * release notes for + link:RelNotes/1.9.5.txt[1.9.5], link:RelNotes/1.9.4.txt[1.9.4], link:RelNotes/1.9.3.txt[1.9.3], link:RelNotes/1.9.2.txt[1.9.2], link:RelNotes/1.9.1.txt[1.9.1], link:RelNotes/1.9.0.txt[1.9.0]. - * link:v1.8.5.5/git.html[documentation for release 1.8.5.5] + * link:v1.8.5.6/git.html[documentation for release 1.8.5.6] * release notes for + link:RelNotes/1.8.5.6.txt[1.8.5.6], link:RelNotes/1.8.5.5.txt[1.8.5.5], link:RelNotes/1.8.5.4.txt[1.8.5.4], link:RelNotes/1.8.5.3.txt[1.8.5.3], @@@ -1104,7 -1103,7 +1108,7 @@@ subscribed to the list to send a messag SEE ALSO -------- linkgit:gittutorial[7], linkgit:gittutorial-2[7], -link:everyday.html[Everyday Git], linkgit:gitcvs-migration[7], +linkgit:giteveryday[7], linkgit:gitcvs-migration[7], linkgit:gitglossary[7], linkgit:gitcore-tutorial[7], linkgit:gitcli[7], link:user-manual.html[The Git User's Manual], linkgit:gitworkflows[7] diff --combined cache.h index 99ed096aed,a258d805cd..22b7b81290 --- a/cache.h +++ b/cache.h @@@ -8,7 -8,6 +8,7 @@@ #include "gettext.h" #include "convert.h" #include "trace.h" +#include "string-list.h" #include SHA1_HEADER #ifndef git_SHA_CTX @@@ -482,7 -481,7 +482,7 @@@ extern int daemonize(void) alloc = (nr); \ else \ alloc = alloc_nr(alloc); \ - x = xrealloc((x), alloc * sizeof(*(x))); \ + REALLOC_ARRAY(x, alloc); \ } \ } while (0) @@@ -570,11 -569,28 +570,11 @@@ extern void fill_stat_cache_info(struc #define REFRESH_IN_PORCELAIN 0x0020 /* user friendly output, not "needs update" */ extern int refresh_index(struct index_state *, unsigned int flags, const struct pathspec *pathspec, char *seen, const char *header_msg); -struct lock_file { - struct lock_file *next; - int fd; - pid_t owner; - char on_list; - char filename[PATH_MAX]; -}; -#define LOCK_DIE_ON_ERROR 1 -#define LOCK_NODEREF 2 -extern int unable_to_lock_error(const char *path, int err); -extern void unable_to_lock_message(const char *path, int err, - struct strbuf *buf); -extern NORETURN void unable_to_lock_index_die(const char *path, int err); -extern int hold_lock_file_for_update(struct lock_file *, const char *path, int); -extern int hold_lock_file_for_append(struct lock_file *, const char *path, int); -extern int commit_lock_file(struct lock_file *); extern void update_index_if_able(struct index_state *, struct lock_file *); extern int hold_locked_index(struct lock_file *, int); extern void set_alternate_index_output(const char *); -extern int close_lock_file(struct lock_file *); -extern void rollback_lock_file(struct lock_file *); + extern int delete_ref(const char *, const unsigned char *sha1, int delopt); /* Environment bits from configuration mechanism */ @@@ -617,6 -633,8 +617,8 @@@ extern int fsync_object_files extern int core_preload_index; extern int core_apply_sparse_checkout; extern int precomposed_unicode; + extern int protect_hfs; + extern int protect_ntfs; /* * The character that begins a commented line in user-editable file @@@ -831,6 -849,7 +833,7 @@@ int normalize_path_copy(char *dst, cons int longest_ancestor_length(const char *path, struct string_list *prefixes); char *strip_path_suffix(const char *path, const char *suffix); int daemon_avoid_alias(const char *path); + extern int is_ntfs_dotgit(const char *name); /* object replacement */ #define LOOKUP_REPLACE_OBJECT 1 @@@ -950,8 -969,8 +953,8 @@@ extern int for_each_abbrev(const char * extern int get_sha1_hex(const char *hex, unsigned char *sha1); extern char *sha1_to_hex(const unsigned char *sha1); /* static buffer result! */ -extern int read_ref_full(const char *refname, unsigned char *sha1, - int reading, int *flags); +extern int read_ref_full(const char *refname, int resolve_flags, + unsigned char *sha1, int *flags); extern int read_ref(const char *refname, unsigned char *sha1); /* @@@ -963,49 -982,29 +966,49 @@@ * or the input ref. * * If the reference cannot be resolved to an object, the behavior - * depends on the "reading" argument: + * depends on the RESOLVE_REF_READING flag: * - * - If reading is set, return NULL. + * - If RESOLVE_REF_READING is set, return NULL. * - * - If reading is not set, clear sha1 and return the name of the last - * reference name in the chain, which will either be a non-symbolic + * - If RESOLVE_REF_READING is not set, clear sha1 and return the name of + * the last reference name in the chain, which will either be a non-symbolic * reference or an undefined reference. If this is a prelude to * "writing" to the ref, the return value is the name of the ref * that will actually be created or changed. * - * If flag is non-NULL, set the value that it points to the + * If the RESOLVE_REF_NO_RECURSE flag is passed, only resolves one + * level of symbolic reference. The value stored in sha1 for a symbolic + * reference will always be null_sha1 in this case, and the return + * value is the reference that the symref refers to directly. + * + * If flags is non-NULL, set the value that it points to the * combination of REF_ISPACKED (if the reference was found among the - * packed references) and REF_ISSYMREF (if the initial reference was a - * symbolic reference). + * packed references), REF_ISSYMREF (if the initial reference was a + * symbolic reference), REF_BAD_NAME (if the reference name is ill + * formed --- see RESOLVE_REF_ALLOW_BAD_NAME below), and REF_ISBROKEN + * (if the ref is malformed or has a bad name). See refs.h for more detail + * on each flag. * * If ref is not a properly-formatted, normalized reference, return * NULL. If more than MAXDEPTH recursive symbolic lookups are needed, * give up and return NULL. * - * errno is set to something meaningful on error. + * RESOLVE_REF_ALLOW_BAD_NAME allows resolving refs even when their + * name is invalid according to git-check-ref-format(1). If the name + * is bad then the value stored in sha1 will be null_sha1 and the two + * flags REF_ISBROKEN and REF_BAD_NAME will be set. + * + * Even with RESOLVE_REF_ALLOW_BAD_NAME, names that escape the refs/ + * directory and do not consist of all caps and underscores cannot be + * resolved. The function returns NULL for such ref names. + * Caps and underscores refers to the special refs, such as HEAD, + * FETCH_HEAD and friends, that all live outside of the refs/ directory. */ -extern const char *resolve_ref_unsafe(const char *ref, unsigned char *sha1, int reading, int *flag); -extern char *resolve_refdup(const char *ref, unsigned char *sha1, int reading, int *flag); +#define RESOLVE_REF_READING 0x01 +#define RESOLVE_REF_NO_RECURSE 0x02 +#define RESOLVE_REF_ALLOW_BAD_NAME 0x04 +extern const char *resolve_ref_unsafe(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); +extern char *resolve_refdup(const char *ref, int resolve_flags, unsigned char *sha1, int *flags); extern int dwim_ref(const char *str, int len, unsigned char *sha1, char **ref); extern int dwim_log(const char *str, int len, unsigned char *sha1, char **ref); @@@ -1041,7 -1040,6 +1044,7 @@@ enum date_mode DATE_SHORT, DATE_LOCAL, DATE_ISO8601, + DATE_ISO8601_STRICT, DATE_RFC2822, DATE_RAW }; @@@ -1049,10 -1047,10 +1052,10 @@@ const char *show_date(unsigned long time, int timezone, enum date_mode mode); void show_date_relative(unsigned long time, int tz, const struct timeval *now, struct strbuf *timebuf); -int parse_date(const char *date, char *buf, int bufsize); +int parse_date(const char *date, struct strbuf *out); int parse_date_basic(const char *date, unsigned long *timestamp, int *offset); int parse_expiry_date(const char *date, unsigned long *timestamp); -void datestamp(char *buf, int bufsize); +void datestamp(struct strbuf *out); #define approxidate(s) approxidate_careful((s), NULL) unsigned long approxidate_careful(const char *, int *); unsigned long approxidate_relative(const char *date, const struct timeval *now); @@@ -1066,7 -1064,6 +1069,7 @@@ extern const char *git_author_info(int) extern const char *git_committer_info(int); extern const char *fmt_ident(const char *name, const char *email, const char *date_str, int); extern const char *fmt_name(const char *name, const char *email); +extern const char *ident_default_name(void); extern const char *ident_default_email(void); extern const char *git_editor(void); extern const char *git_pager(int stdout_is_tty); @@@ -1145,7 -1142,7 +1148,7 @@@ extern void prepare_alt_odb(void) extern void read_info_alternates(const char * relative_base, int depth); extern void add_to_alternates_file(const char *reference); typedef int alt_odb_fn(struct alternate_object_database *, void *); -extern void foreach_alt_odb(alt_odb_fn, void*); +extern int foreach_alt_odb(alt_odb_fn, void*); struct pack_window { struct pack_window *next; @@@ -1241,50 -1238,6 +1244,50 @@@ extern unsigned long unpack_object_head extern unsigned long get_size_from_delta(struct packed_git *, struct pack_window **, off_t); extern int unpack_object_header(struct packed_git *, struct pack_window **, off_t *, unsigned long *); +/* + * Iterate over the files in the loose-object parts of the object + * directory "path", triggering the following callbacks: + * + * - loose_object is called for each loose object we find. + * + * - loose_cruft is called for any files that do not appear to be + * loose objects. Note that we only look in the loose object + * directories "objects/[0-9a-f]{2}/", so we will not report + * "objects/foobar" as cruft. + * + * - loose_subdir is called for each top-level hashed subdirectory + * of the object directory (e.g., "$OBJDIR/f0"). It is called + * after the objects in the directory are processed. + * + * Any callback that is NULL will be ignored. Callbacks returning non-zero + * will end the iteration. + */ +typedef int each_loose_object_fn(const unsigned char *sha1, + const char *path, + void *data); +typedef int each_loose_cruft_fn(const char *basename, + const char *path, + void *data); +typedef int each_loose_subdir_fn(int nr, + const char *path, + void *data); +int for_each_loose_file_in_objdir(const char *path, + each_loose_object_fn obj_cb, + each_loose_cruft_fn cruft_cb, + each_loose_subdir_fn subdir_cb, + void *data); + +/* + * Iterate over loose and packed objects in both the local + * repository and any alternates repositories. + */ +typedef int each_packed_object_fn(const unsigned char *sha1, + struct packed_git *pack, + uint32_t pos, + void *data); +extern int for_each_loose_object(each_loose_object_fn, void *); +extern int for_each_packed_object(each_packed_object_fn, void *); + struct object_info { /* Request */ enum object_type *typep; @@@ -1346,7 -1299,7 +1349,7 @@@ extern int git_config_from_buf(config_f const char *buf, size_t len, void *data); extern void git_config_push_parameter(const char *text); extern int git_config_from_parameters(config_fn_t fn, void *data); -extern int git_config(config_fn_t fn, void *); +extern void git_config(config_fn_t fn, void *); extern int git_config_with_options(config_fn_t fn, void *, struct git_config_source *config_source, int respect_includes); @@@ -1370,7 -1323,6 +1373,7 @@@ extern int git_config_rename_section_in 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 unsigned long git_env_ulong(const char *, unsigned long); extern int git_config_system(void); extern int config_error_nonbool(const char *); #if defined(__GNUC__) @@@ -1404,69 -1356,6 +1407,69 @@@ extern int parse_config_key(const char const char **subsection, int *subsection_len, const char **key); +struct config_set_element { + struct hashmap_entry ent; + char *key; + struct string_list value_list; +}; + +struct configset_list_item { + struct config_set_element *e; + int value_index; +}; + +/* + * the contents of the list are ordered according to their + * position in the config files and order of parsing the files. + * (i.e. key-value pair at the last position of .git/config will + * be at the last item of the list) + */ +struct configset_list { + struct configset_list_item *items; + unsigned int nr, alloc; +}; + +struct config_set { + struct hashmap config_hash; + int hash_initialized; + struct configset_list list; +}; + +extern void git_configset_init(struct config_set *cs); +extern int git_configset_add_file(struct config_set *cs, const char *filename); +extern int git_configset_get_value(struct config_set *cs, const char *key, const char **value); +extern const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key); +extern void git_configset_clear(struct config_set *cs); +extern int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest); +extern int git_configset_get_string(struct config_set *cs, const char *key, char **dest); +extern int git_configset_get_int(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest); +extern int git_configset_get_bool(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_bool_or_int(struct config_set *cs, const char *key, int *is_bool, int *dest); +extern int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest); +extern int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest); + +extern int git_config_get_value(const char *key, const char **value); +extern const struct string_list *git_config_get_value_multi(const char *key); +extern void git_config_clear(void); +extern void git_config_iter(config_fn_t fn, void *data); +extern int git_config_get_string_const(const char *key, const char **dest); +extern int git_config_get_string(const char *key, char **dest); +extern int git_config_get_int(const char *key, int *dest); +extern int git_config_get_ulong(const char *key, unsigned long *dest); +extern int git_config_get_bool(const char *key, int *dest); +extern int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest); +extern int git_config_get_maybe_bool(const char *key, int *dest); +extern int git_config_get_pathname(const char *key, const char **dest); + +struct key_value_info { + const char *filename; + int linenr; +}; + +extern NORETURN void git_die_config(const char *key, const char *err, ...) __attribute__((format(printf, 2, 3))); +extern NORETURN void git_die_config_linenr(const char *key, const char *filename, int linenr); + extern int committer_ident_sufficiently_given(void); extern int author_ident_sufficiently_given(void); @@@ -1477,8 -1366,6 +1480,8 @@@ extern const char *git_mailmap_blob /* IO helper functions */ extern void maybe_flush_or_die(FILE *, const char *); +__attribute__((format (printf, 2, 3))) +extern void fprintf_or_die(FILE *, const char *fmt, ...); extern int copy_fd(int ifd, int ofd); extern int copy_file(const char *dst, const char *src, int mode); extern int copy_file_with_time(const char *dst, const char *src, int mode); diff --combined config.c index 15a2983577,73d8205ae4..d5446d2b63 --- a/config.c +++ b/config.c @@@ -6,12 -6,9 +6,12 @@@ * */ #include "cache.h" +#include "lockfile.h" #include "exec_cmd.h" #include "strbuf.h" #include "quote.h" +#include "hashmap.h" +#include "string-list.h" struct config_source { struct config_source *prev; @@@ -40,13 -37,6 +40,13 @@@ static struct config_source *cf static int zlib_compression_seen; +/* + * Default config_set that contains key-value pairs from the usual set of config + * config files (i.e repo specific .git/config, user wide ~/.gitconfig, XDG + * config file and the global /etc/gitconfig) + */ +static struct config_set the_config_set; + static int config_file_fgetc(struct config_source *conf) { return fgetc(conf->u.file); @@@ -137,6 -127,7 +137,6 @@@ static int handle_path_include(const ch int git_config_include(const char *var, const char *value, void *data) { struct config_include_data *inc = data; - const char *type; int ret; /* @@@ -147,7 -138,10 +147,7 @@@ if (ret < 0) return ret; - if (!skip_prefix(var, "include.", &type)) - return ret; - - if (!strcmp(type, "path")) + if (!strcmp(var, "include.path")) ret = handle_path_include(value, inc); return ret; } @@@ -243,7 -237,6 +243,7 @@@ static int get_next_char(void cf->linenr++; if (c == EOF) { cf->eof = 1; + cf->linenr++; c = '\n'; } return c; @@@ -319,7 -312,6 +319,7 @@@ static int get_value(config_fn_t fn, vo { int c; char *value; + int ret; /* Get the full name */ for (;;) { @@@ -342,15 -334,7 +342,15 @@@ if (!value) return -1; } - return fn(name->buf, value, data); + /* + * We already consumed the \n, but we need linenr to point to + * the line we just parsed during the call to fn to get + * accurate line number in error messages. + */ + cf->linenr--; + ret = fn(name->buf, value, data); + cf->linenr++; + return ret; } static int get_extended_base_var(struct strbuf *name, int c) @@@ -466,9 -450,9 +466,9 @@@ static int git_parse_source(config_fn_ break; } if (cf->die_on_error) - die("bad config file line %d in %s", cf->linenr, cf->name); + die(_("bad config file line %d in %s"), cf->linenr, cf->name); else - return error("bad config file line %d in %s", cf->linenr, cf->name); + return error(_("bad config file line %d in %s"), cf->linenr, cf->name); } static int parse_unit_factor(const char *end, uintmax_t *val) @@@ -584,9 -568,9 +584,9 @@@ static void die_bad_number(const char * value = ""; if (cf && cf->name) - die("bad numeric config value '%s' for '%s' in %s: %s", + die(_("bad numeric config value '%s' for '%s' in %s: %s"), value, name, cf->name, reason); - die("bad numeric config value '%s' for '%s': %s", value, name, reason); + die(_("bad numeric config value '%s' for '%s': %s"), value, name, reason); } int git_config_int(const char *name, const char *value) @@@ -671,7 -655,7 +671,7 @@@ int git_config_pathname(const char **de return config_error_nonbool(var); *dest = expand_user_path(value); if (!*dest) - die("Failed to expand user dir in: '%s'", value); + die(_("failed to expand user dir in: '%s'"), value); return 0; } @@@ -749,7 -733,7 +749,7 @@@ static int git_default_core_config(cons if (level == -1) level = Z_DEFAULT_COMPRESSION; else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad zlib compression level %d", level); + die(_("bad zlib compression level %d"), level); zlib_compression_level = level; zlib_compression_seen = 1; return 0; @@@ -760,7 -744,7 +760,7 @@@ if (level == -1) level = Z_DEFAULT_COMPRESSION; else if (level < 0 || level > Z_BEST_COMPRESSION) - die("bad zlib compression level %d", level); + die(_("bad zlib compression level %d"), level); core_compression_level = level; core_compression_seen = 1; if (!zlib_compression_seen) @@@ -882,7 -866,7 +882,7 @@@ else if (!strcmp(value, "link")) object_creation_mode = OBJECT_CREATION_USES_HARDLINKS; else - die("Invalid mode for object creation: %s", value); + die(_("invalid mode for object creation: %s"), value); return 0; } @@@ -896,6 -880,16 +896,16 @@@ return 0; } + if (!strcmp(var, "core.protecthfs")) { + protect_hfs = git_config_bool(var, value); + return 0; + } + + if (!strcmp(var, "core.protectntfs")) { + protect_ntfs = git_config_bool(var, value); + return 0; + } + /* Add other config variables here and to Documentation/config.txt. */ return 0; } @@@ -1140,28 -1134,12 +1150,28 @@@ const char *git_etc_gitconfig(void return system_wide; } +/* + * Parse environment variable 'k' as a boolean (in various + * possible spellings); if missing, use the default value 'def'. + */ int git_env_bool(const char *k, int def) { const char *v = getenv(k); return v ? git_config_bool(k, v) : def; } +/* + * Parse environment variable 'k' as ulong with possibly a unit + * suffix; if missing, use the default value 'val'. + */ +unsigned long git_env_ulong(const char *k, unsigned long val) +{ + const char *v = getenv(k); + if (v && !git_parse_ulong(v, &val)) + die("failed to parse %s", k); + return val; +} + int git_config_system(void) { return !git_env_bool("GIT_CONFIG_NOSYSTEM", 0); @@@ -1198,7 -1176,7 +1208,7 @@@ int git_config_early(config_fn_t fn, vo switch (git_config_from_parameters(fn, data)) { case -1: /* error */ - die("unable to parse command-line config"); + die(_("unable to parse command-line config")); break; case 0: /* found nothing */ break; @@@ -1245,365 -1223,9 +1255,365 @@@ int git_config_with_options(config_fn_ return ret; } -int git_config(config_fn_t fn, void *data) +static void git_config_raw(config_fn_t fn, void *data) +{ + if (git_config_with_options(fn, data, NULL, 1) < 0) + /* + * git_config_with_options() normally returns only + * positive values, as most errors are fatal, and + * non-fatal potential errors are guarded by "if" + * statements that are entered only when no error is + * possible. + * + * If we ever encounter a non-fatal error, it means + * something went really wrong and we should stop + * immediately. + */ + die(_("unknown error occured while reading the configuration files")); +} + +static void configset_iter(struct config_set *cs, config_fn_t fn, void *data) +{ + int i, value_index; + struct string_list *values; + struct config_set_element *entry; + struct configset_list *list = &cs->list; + struct key_value_info *kv_info; + + for (i = 0; i < list->nr; i++) { + entry = list->items[i].e; + value_index = list->items[i].value_index; + values = &entry->value_list; + if (fn(entry->key, values->items[value_index].string, data) < 0) { + kv_info = values->items[value_index].util; + git_die_config_linenr(entry->key, kv_info->filename, kv_info->linenr); + } + } +} + +static void git_config_check_init(void); + +void git_config(config_fn_t fn, void *data) +{ + git_config_check_init(); + configset_iter(&the_config_set, fn, data); +} + +static struct config_set_element *configset_find_element(struct config_set *cs, const char *key) +{ + struct config_set_element k; + struct config_set_element *found_entry; + char *normalized_key; + int ret; + /* + * `key` may come from the user, so normalize it before using it + * for querying entries from the hashmap. + */ + ret = git_config_parse_key(key, &normalized_key, NULL); + + if (ret) + return NULL; + + hashmap_entry_init(&k, strhash(normalized_key)); + k.key = normalized_key; + found_entry = hashmap_get(&cs->config_hash, &k, NULL); + free(normalized_key); + return found_entry; +} + +static int configset_add_value(struct config_set *cs, const char *key, const char *value) +{ + struct config_set_element *e; + struct string_list_item *si; + struct configset_list_item *l_item; + struct key_value_info *kv_info = xmalloc(sizeof(*kv_info)); + + e = configset_find_element(cs, key); + /* + * Since the keys are being fed by git_config*() callback mechanism, they + * are already normalized. So simply add them without any further munging. + */ + if (!e) { + e = xmalloc(sizeof(*e)); + hashmap_entry_init(e, strhash(key)); + e->key = xstrdup(key); + string_list_init(&e->value_list, 1); + hashmap_add(&cs->config_hash, e); + } + si = string_list_append_nodup(&e->value_list, value ? xstrdup(value) : NULL); + + ALLOC_GROW(cs->list.items, cs->list.nr + 1, cs->list.alloc); + l_item = &cs->list.items[cs->list.nr++]; + l_item->e = e; + l_item->value_index = e->value_list.nr - 1; + + if (cf) { + kv_info->filename = strintern(cf->name); + kv_info->linenr = cf->linenr; + } else { + /* for values read from `git_config_from_parameters()` */ + kv_info->filename = NULL; + kv_info->linenr = -1; + } + si->util = kv_info; + + return 0; +} + +static int config_set_element_cmp(const struct config_set_element *e1, + const struct config_set_element *e2, const void *unused) +{ + return strcmp(e1->key, e2->key); +} + +void git_configset_init(struct config_set *cs) +{ + hashmap_init(&cs->config_hash, (hashmap_cmp_fn)config_set_element_cmp, 0); + cs->hash_initialized = 1; + cs->list.nr = 0; + cs->list.alloc = 0; + cs->list.items = NULL; +} + +void git_configset_clear(struct config_set *cs) +{ + struct config_set_element *entry; + struct hashmap_iter iter; + if (!cs->hash_initialized) + return; + + hashmap_iter_init(&cs->config_hash, &iter); + while ((entry = hashmap_iter_next(&iter))) { + free(entry->key); + string_list_clear(&entry->value_list, 1); + } + hashmap_free(&cs->config_hash, 1); + cs->hash_initialized = 0; + free(cs->list.items); + cs->list.nr = 0; + cs->list.alloc = 0; + cs->list.items = NULL; +} + +static int config_set_callback(const char *key, const char *value, void *cb) +{ + struct config_set *cs = cb; + configset_add_value(cs, key, value); + return 0; +} + +int git_configset_add_file(struct config_set *cs, const char *filename) +{ + return git_config_from_file(config_set_callback, filename, cs); +} + +int git_configset_get_value(struct config_set *cs, const char *key, const char **value) +{ + const struct string_list *values = NULL; + /* + * Follows "last one wins" semantic, i.e., if there are multiple matches for the + * queried key in the files of the configset, the value returned will be the last + * value in the value list for that key. + */ + values = git_configset_get_value_multi(cs, key); + + if (!values) + return 1; + assert(values->nr > 0); + *value = values->items[values->nr - 1].string; + return 0; +} + +const struct string_list *git_configset_get_value_multi(struct config_set *cs, const char *key) { - return git_config_with_options(fn, data, NULL, 1); + struct config_set_element *e = configset_find_element(cs, key); + return e ? &e->value_list : NULL; +} + +int git_configset_get_string_const(struct config_set *cs, const char *key, const char **dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) + return git_config_string(dest, key, value); + else + return 1; +} + +int git_configset_get_string(struct config_set *cs, const char *key, char **dest) +{ + return git_configset_get_string_const(cs, key, (const char **)dest); +} + +int git_configset_get_int(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_int(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_ulong(struct config_set *cs, const char *key, unsigned long *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_ulong(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_bool(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_bool(key, value); + return 0; + } else + return 1; +} + +int git_configset_get_bool_or_int(struct config_set *cs, const char *key, + int *is_bool, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_bool_or_int(key, value, is_bool); + return 0; + } else + return 1; +} + +int git_configset_get_maybe_bool(struct config_set *cs, const char *key, int *dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) { + *dest = git_config_maybe_bool(key, value); + if (*dest == -1) + return -1; + return 0; + } else + return 1; +} + +int git_configset_get_pathname(struct config_set *cs, const char *key, const char **dest) +{ + const char *value; + if (!git_configset_get_value(cs, key, &value)) + return git_config_pathname(dest, key, value); + else + return 1; +} + +static void git_config_check_init(void) +{ + if (the_config_set.hash_initialized) + return; + git_configset_init(&the_config_set); + git_config_raw(config_set_callback, &the_config_set); +} + +void git_config_clear(void) +{ + if (!the_config_set.hash_initialized) + return; + git_configset_clear(&the_config_set); +} + +int git_config_get_value(const char *key, const char **value) +{ + git_config_check_init(); + return git_configset_get_value(&the_config_set, key, value); +} + +const struct string_list *git_config_get_value_multi(const char *key) +{ + git_config_check_init(); + return git_configset_get_value_multi(&the_config_set, key); +} + +int git_config_get_string_const(const char *key, const char **dest) +{ + int ret; + git_config_check_init(); + ret = git_configset_get_string_const(&the_config_set, key, dest); + if (ret < 0) + git_die_config(key, NULL); + return ret; +} + +int git_config_get_string(const char *key, char **dest) +{ + git_config_check_init(); + return git_config_get_string_const(key, (const char **)dest); +} + +int git_config_get_int(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_int(&the_config_set, key, dest); +} + +int git_config_get_ulong(const char *key, unsigned long *dest) +{ + git_config_check_init(); + return git_configset_get_ulong(&the_config_set, key, dest); +} + +int git_config_get_bool(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_bool(&the_config_set, key, dest); +} + +int git_config_get_bool_or_int(const char *key, int *is_bool, int *dest) +{ + git_config_check_init(); + return git_configset_get_bool_or_int(&the_config_set, key, is_bool, dest); +} + +int git_config_get_maybe_bool(const char *key, int *dest) +{ + git_config_check_init(); + return git_configset_get_maybe_bool(&the_config_set, key, dest); +} + +int git_config_get_pathname(const char *key, const char **dest) +{ + int ret; + git_config_check_init(); + ret = git_configset_get_pathname(&the_config_set, key, dest); + if (ret < 0) + git_die_config(key, NULL); + return ret; +} + +NORETURN +void git_die_config_linenr(const char *key, const char *filename, int linenr) +{ + if (!filename) + die(_("unable to parse '%s' from command-line config"), key); + else + die(_("bad config variable '%s' in file '%s' at line %d"), + key, filename, linenr); +} + +NORETURN __attribute__((format(printf, 2, 3))) +void git_die_config(const char *key, const char *err, ...) +{ + const struct string_list *values; + struct key_value_info *kv_info; + + if (err) { + va_list params; + va_start(params, err); + vreportf("error: ", err, params); + va_end(params); + } + values = git_config_get_value_multi(key); + kv_info = values->items[values->nr - 1].util; + git_die_config_linenr(key, kv_info->filename, kv_info->linenr); } /* @@@ -1644,7 -1266,7 +1654,7 @@@ static int store_aux(const char *key, c case KEY_SEEN: if (matches(key, value)) { if (store.seen == 1 && store.multi_replace == 0) { - warning("%s has multiple values", key); + warning(_("%s has multiple values"), key); } ALLOC_GROW(store.offset, store.seen + 1, @@@ -2041,9 -1663,9 +2051,9 @@@ int git_config_set_multivar_in_file(con MAP_PRIVATE, in_fd, 0); close(in_fd); - if (chmod(lock->filename, st.st_mode & 07777) < 0) { + if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { error("chmod on %s failed: %s", - lock->filename, strerror(errno)); + lock->filename.buf, strerror(errno)); ret = CONFIG_NO_WRITE; goto out_free; } @@@ -2100,7 -1722,6 +2110,7 @@@ if (commit_lock_file(lock) < 0) { error("could not commit config file %s", config_filename); ret = CONFIG_NO_WRITE; + lock = NULL; goto out_free; } @@@ -2113,9 -1734,6 +2123,9 @@@ lock = NULL; ret = 0; + /* Invalidate the config cache */ + git_config_clear(); + out_free: if (lock) rollback_lock_file(lock); @@@ -2123,7 -1741,7 +2133,7 @@@ return ret; write_err_out: - ret = write_error(lock->filename); + ret = write_error(lock->filename.buf); goto out_free; } @@@ -2224,9 -1842,9 +2234,9 @@@ int git_config_rename_section_in_file(c fstat(fileno(config_file), &st); - if (chmod(lock->filename, st.st_mode & 07777) < 0) { + if (chmod(lock->filename.buf, st.st_mode & 07777) < 0) { ret = error("chmod on %s failed: %s", - lock->filename, strerror(errno)); + lock->filename.buf, strerror(errno)); goto out; } @@@ -2247,7 -1865,7 +2257,7 @@@ } store.baselen = strlen(new_name); if (!store_write_section(out_fd, new_name)) { - ret = write_error(lock->filename); + ret = write_error(lock->filename.buf); goto out; } /* @@@ -2273,7 -1891,7 +2283,7 @@@ continue; length = strlen(output); if (write_in_full(out_fd, output, length) != length) { - ret = write_error(lock->filename); + ret = write_error(lock->filename.buf); goto out; } } diff --combined config.mak.uname index a2f380fd8d,0941e30a67..f3c93f27c9 --- a/config.mak.uname +++ b/config.mak.uname @@@ -89,13 -89,8 +89,13 @@@ ifeq ($(uname_S),Darwin NEEDS_CRYPTO_WITH_SSL = YesPlease NEEDS_SSL_WITH_CRYPTO = YesPlease NEEDS_LIBICONV = YesPlease + # Note: $(uname_R) gives us the underlying Darwin version. + # - MacOS 10.0.* and MacOS 10.1.0 = Darwin 1.* + # - MacOS 10.x.* = Darwin (x+4).* for (1 <= x) + # i.e. "begins with [15678] and a dot" means "10.4.* or older". ifeq ($(shell expr "$(uname_R)" : '[15678]\.'),2) OLD_ICONV = UnfortunatelyYes + NO_APPLE_COMMON_CRYPTO = YesPlease endif ifeq ($(shell expr "$(uname_R)" : '[15]\.'),2) NO_STRLCPY = YesPlease @@@ -105,6 -100,7 +105,7 @@@ HAVE_DEV_TTY = YesPlease COMPAT_OBJS += compat/precompose_utf8.o BASIC_CFLAGS += -DPRECOMPOSE_UNICODE + BASIC_CFLAGS += -DPROTECT_HFS_DEFAULT=1 endif ifeq ($(uname_S),SunOS) NEEDS_SOCKET = YesPlease @@@ -373,6 -369,7 +374,7 @@@ ifeq ($(uname_S),Windows EXTLIBS = user32.lib advapi32.lib shell32.lib wininet.lib ws2_32.lib invalidcontinue.obj PTHREAD_LIBS = lib = + BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 ifndef DEBUG BASIC_CFLAGS += -GL -Os -MD BASIC_LDFLAGS += -LTCG @@@ -514,6 -511,7 +516,7 @@@ ifneq (,$(findstring MINGW,$(uname_S)) COMPAT_OBJS += compat/mingw.o compat/winansi.o \ compat/win32/pthread.o compat/win32/syslog.o \ compat/win32/dirent.o + BASIC_CFLAGS += -DPROTECT_NTFS_DEFAULT=1 BASIC_LDFLAGS += -Wl,--large-address-aware EXTLIBS += -lws2_32 GITLIBS += git.res diff --combined fsck.c index 2fffa434a5,50c6507d60..0324194631 --- a/fsck.c +++ b/fsck.c @@@ -6,7 -6,7 +6,8 @@@ #include "commit.h" #include "tag.h" #include "fsck.h" +#include "refs.h" + #include "utf8.h" static int fsck_walk_tree(struct tree *tree, fsck_walk_func walk, void *data) { @@@ -171,7 -171,9 +172,9 @@@ static int fsck_tree(struct tree *item has_empty_name |= !*name; has_dot |= !strcmp(name, "."); has_dotdot |= !strcmp(name, ".."); - has_dotgit |= !strcmp(name, ".git"); + has_dotgit |= (!strcmp(name, ".git") || + is_hfs_dotgit(name) || + is_ntfs_dotgit(name)); has_zero_pad |= *(char *)desc.buffer == '0'; update_tree_entry(&desc); @@@ -238,26 -240,6 +241,26 @@@ return retval; } +static int require_end_of_header(const void *data, unsigned long size, + struct object *obj, fsck_error error_func) +{ + const char *buffer = (const char *)data; + unsigned long i; + + for (i = 0; i < size; i++) { + switch (buffer[i]) { + case '\0': + return error_func(obj, FSCK_ERROR, + "unterminated header: NUL at offset %d", i); + case '\n': + if (i + 1 < size && buffer[i + 1] == '\n') + return 0; + } + } + + return error_func(obj, FSCK_ERROR, "unterminated header"); +} + static int fsck_ident(const char **ident, struct object *obj, fsck_error error_func) { char *end; @@@ -298,16 -280,13 +301,16 @@@ } static int fsck_commit_buffer(struct commit *commit, const char *buffer, - fsck_error error_func) + unsigned long size, fsck_error error_func) { unsigned char tree_sha1[20], sha1[20]; struct commit_graft *graft; unsigned parent_count, parent_line_count = 0; int err; + if (require_end_of_header(buffer, size, &commit->object, error_func)) + return -1; + if (!skip_prefix(buffer, "tree ", &buffer)) return error_func(&commit->object, FSCK_ERROR, "invalid format - expected 'tree' line"); if (get_sha1_hex(buffer, tree_sha1) || buffer[40] != '\n') @@@ -346,111 -325,24 +349,111 @@@ return 0; } -static int fsck_commit(struct commit *commit, fsck_error error_func) +static int fsck_commit(struct commit *commit, const char *data, + unsigned long size, fsck_error error_func) { - const char *buffer = get_commit_buffer(commit, NULL); - int ret = fsck_commit_buffer(commit, buffer, error_func); - unuse_commit_buffer(commit, buffer); + const char *buffer = data ? data : get_commit_buffer(commit, &size); + int ret = fsck_commit_buffer(commit, buffer, size, error_func); + if (!data) + unuse_commit_buffer(commit, buffer); return ret; } -static int fsck_tag(struct tag *tag, fsck_error error_func) +static int fsck_tag_buffer(struct tag *tag, const char *data, + unsigned long size, fsck_error error_func) +{ + unsigned char sha1[20]; + int ret = 0; + const char *buffer; + char *to_free = NULL, *eol; + struct strbuf sb = STRBUF_INIT; + + if (data) + buffer = data; + else { + enum object_type type; + + buffer = to_free = + read_sha1_file(tag->object.sha1, &type, &size); + if (!buffer) + return error_func(&tag->object, FSCK_ERROR, + "cannot read tag object"); + + if (type != OBJ_TAG) { + ret = error_func(&tag->object, FSCK_ERROR, + "expected tag got %s", + typename(type)); + goto done; + } + } + + if (require_end_of_header(buffer, size, &tag->object, error_func)) + goto done; + + if (!skip_prefix(buffer, "object ", &buffer)) { + ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'object' line"); + goto done; + } + if (get_sha1_hex(buffer, sha1) || buffer[40] != '\n') { + ret = error_func(&tag->object, FSCK_ERROR, "invalid 'object' line format - bad sha1"); + goto done; + } + buffer += 41; + + if (!skip_prefix(buffer, "type ", &buffer)) { + ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'type' line"); + goto done; + } + eol = strchr(buffer, '\n'); + if (!eol) { + ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line"); + goto done; + } + if (type_from_string_gently(buffer, eol - buffer, 1) < 0) + ret = error_func(&tag->object, FSCK_ERROR, "invalid 'type' value"); + if (ret) + goto done; + buffer = eol + 1; + + if (!skip_prefix(buffer, "tag ", &buffer)) { + ret = error_func(&tag->object, FSCK_ERROR, "invalid format - expected 'tag' line"); + goto done; + } + eol = strchr(buffer, '\n'); + if (!eol) { + ret = error_func(&tag->object, FSCK_ERROR, "invalid format - unexpected end after 'type' line"); + goto done; + } + strbuf_addf(&sb, "refs/tags/%.*s", (int)(eol - buffer), buffer); + if (check_refname_format(sb.buf, 0)) + error_func(&tag->object, FSCK_WARN, "invalid 'tag' name: %s", buffer); + buffer = eol + 1; + + if (!skip_prefix(buffer, "tagger ", &buffer)) + /* early tags do not contain 'tagger' lines; warn only */ + error_func(&tag->object, FSCK_WARN, "invalid format - expected 'tagger' line"); + else + ret = fsck_ident(&buffer, &tag->object, error_func); + +done: + strbuf_release(&sb); + free(to_free); + return ret; +} + +static int fsck_tag(struct tag *tag, const char *data, + unsigned long size, fsck_error error_func) { struct object *tagged = tag->tagged; if (!tagged) return error_func(&tag->object, FSCK_ERROR, "could not load tagged object"); - return 0; + + return fsck_tag_buffer(tag, data, size, error_func); } -int fsck_object(struct object *obj, int strict, fsck_error error_func) +int fsck_object(struct object *obj, void *data, unsigned long size, + int strict, fsck_error error_func) { if (!obj) return error_func(obj, FSCK_ERROR, "no valid object to fsck"); @@@ -460,11 -352,9 +463,11 @@@ if (obj->type == OBJ_TREE) return fsck_tree((struct tree *) obj, strict, error_func); if (obj->type == OBJ_COMMIT) - return fsck_commit((struct commit *) obj, error_func); + return fsck_commit((struct commit *) obj, (const char *) data, + size, error_func); if (obj->type == OBJ_TAG) - return fsck_tag((struct tag *) obj, error_func); + return fsck_tag((struct tag *) obj, (const char *) data, + size, error_func); return error_func(obj, FSCK_ERROR, "unknown type '%d' (internal fsck error)", obj->type); diff --combined path.c index f68df0cf88,757d0b057d..e608993801 --- a/path.c +++ b/path.c @@@ -148,12 -148,10 +148,12 @@@ void home_config_paths(char **global, c *global = mkpathdup("%s/.gitconfig", home); } - if (!xdg_home) - *xdg = NULL; - else - *xdg = mkpathdup("%s/git/%s", xdg_home, file); + if (xdg) { + if (!xdg_home) + *xdg = NULL; + else + *xdg = mkpathdup("%s/git/%s", xdg_home, file); + } free(to_free); } @@@ -823,3 -821,36 +823,36 @@@ int daemon_avoid_alias(const char *p } } } + + static int only_spaces_and_periods(const char *path, size_t len, size_t skip) + { + if (len < skip) + return 0; + len -= skip; + path += skip; + while (len-- > 0) { + char c = *(path++); + if (c != ' ' && c != '.') + return 0; + } + return 1; + } + + int is_ntfs_dotgit(const char *name) + { + int len; + + for (len = 0; ; len++) + if (!name[len] || name[len] == '\\' || is_dir_sep(name[len])) { + if (only_spaces_and_periods(name, len, 4) && + !strncasecmp(name, ".git", 4)) + return 1; + if (only_spaces_and_periods(name, len, 5) && + !strncasecmp(name, "git~1", 5)) + return 1; + if (name[len] != '\\') + return 0; + name += len + 1; + len = -1; + } + } diff --combined read-cache.c index 8f3e9eb314,f042693856..9cff715d6b --- a/read-cache.c +++ b/read-cache.c @@@ -5,7 -5,6 +5,7 @@@ */ #define NO_THE_INDEX_COMPATIBILITY_MACROS #include "cache.h" +#include "lockfile.h" #include "cache-tree.h" #include "refs.h" #include "dir.h" @@@ -17,6 -16,7 +17,7 @@@ #include "varint.h" #include "split-index.h" #include "sigchain.h" + #include "utf8.h" static struct cache_entry *refresh_cache_entry(struct cache_entry *ce, unsigned int options); @@@ -776,9 -776,10 +777,10 @@@ static int verify_dotfile(const char *r * shares the path end test with the ".." case. */ case 'g': - if (rest[1] != 'i') + case 'G': + if (rest[1] != 'i' && rest[1] != 'I') break; - if (rest[2] != 't') + if (rest[2] != 't' && rest[2] != 'T') break; rest += 2; /* fallthrough */ @@@ -802,6 -803,10 +804,10 @@@ int verify_path(const char *path return 1; if (is_dir_sep(c)) { inside: + if (protect_hfs && is_hfs_dotgit(path)) + return 0; + if (protect_ntfs && is_ntfs_dotgit(path)) + return 0; c = *path++; if ((c == '.' && !verify_dotfile(path)) || is_dir_sep(c) || c == '\0') @@@ -1247,16 -1252,24 +1253,16 @@@ static struct cache_entry *refresh_cach #define INDEX_FORMAT_DEFAULT 3 -static int index_format_config(const char *var, const char *value, void *cb) -{ - unsigned int *version = cb; - if (!strcmp(var, "index.version")) { - *version = git_config_int(var, value); - return 0; - } - return 1; -} - static unsigned int get_index_format_default(void) { char *envversion = getenv("GIT_INDEX_VERSION"); char *endp; + int value; unsigned int version = INDEX_FORMAT_DEFAULT; if (!envversion) { - git_config(index_format_config, &version); + if (!git_config_get_int("index.version", &value)) + version = value; if (version < INDEX_FORMAT_LB || INDEX_FORMAT_UB < version) { warning(_("index.version set, but the value is invalid.\n" "Using version %i"), INDEX_FORMAT_DEFAULT); @@@ -1368,14 -1381,6 +1374,14 @@@ static int read_index_extension(struct return 0; } +int hold_locked_index(struct lock_file *lk, int die_on_error) +{ + return hold_lock_file_for_update(lk, get_index_file(), + die_on_error + ? LOCK_DIE_ON_ERROR + : 0); +} + int read_index(struct index_state *istate) { return read_index_from(istate, get_index_file()); @@@ -1474,21 -1479,6 +1480,21 @@@ static struct cache_entry *create_from_ return ce; } +static void check_ce_order(struct cache_entry *ce, struct cache_entry *next_ce) +{ + int name_compare = strcmp(ce->name, next_ce->name); + if (0 < name_compare) + die("unordered stage entries in index"); + if (!name_compare) { + if (!ce_stage(ce)) + die("multiple stage entries for merged file '%s'", + ce->name); + if (ce_stage(ce) > ce_stage(next_ce)) + die("unordered stage entries for '%s'", + ce->name); + } +} + /* remember to discard_cache() before reading a different cache! */ int do_read_index(struct index_state *istate, const char *path, int must_exist) { @@@ -1550,9 -1540,6 +1556,9 @@@ ce = create_from_disk(disk_ce, &consumed, previous_name); set_index_entry(istate, i, ce); + if (i > 0) + check_ce_order(istate->cache[i - 1], ce); + src_offset += consumed; } strbuf_release(&previous_name_buf); @@@ -2050,10 -2037,16 +2056,10 @@@ void set_alternate_index_output(const c static int commit_locked_index(struct lock_file *lk) { - if (alternate_index_output) { - if (lk->fd >= 0 && close_lock_file(lk)) - return -1; - if (rename(lk->filename, alternate_index_output)) - return -1; - lk->filename[0] = 0; - return 0; - } else { + if (alternate_index_output) + return commit_lock_file_to(lk, alternate_index_output); + else return commit_lock_file(lk); - } } static int do_write_locked_index(struct index_state *istate, struct lock_file *lock, @@@ -2193,6 -2186,7 +2199,6 @@@ int read_index_unmerged(struct index_st if (add_index_entry(istate, new_ce, 0)) return error("%s: cannot drop to stage #0", new_ce->name); - i = index_name_pos(istate, new_ce->name, len); } return unmerged; } diff --combined t/t1450-fsck.sh index 019fddd4e8,426f753fe3..d00b70f99d --- a/t/t1450-fsck.sh +++ b/t/t1450-fsck.sh @@@ -214,44 -214,6 +214,44 @@@ test_expect_success 'tag pointing to so test_must_fail git fsck --tags ' +test_expect_success 'tag with incorrect tag name & missing tagger' ' + sha=$(git rev-parse HEAD) && + cat >wrong-tag <<-EOF && + object $sha + type commit + tag wrong name format + + This is an invalid tag. + EOF + + tag=$(git hash-object -t tag -w --stdin .git/refs/tags/wrong && + test_when_finished "git update-ref -d refs/tags/wrong" && + git fsck --tags 2>out && + grep "invalid .tag. name" out && + grep "expected .tagger. line" out +' + +test_expect_success 'tag with bad tagger' ' + sha=$(git rev-parse HEAD) && + cat >wrong-tag <<-EOF && + object $sha + type commit + tag not-quite-wrong + tagger Bad Tagger Name + + This is an invalid tag. + EOF + + tag=$(git hash-object --literally -t tag -w --stdin .git/refs/tags/wrong && + test_when_finished "git update-ref -d refs/tags/wrong" && + test_must_fail git fsck --tags 2>out && + grep "error in tag .*: invalid author/committer" out +' + test_expect_success 'cleaned up' ' git fsck >actual 2>&1 && test_cmp empty actual @@@ -309,36 -271,41 +309,41 @@@ test_expect_success 'fsck notices submo ) ' - test_expect_success 'fsck notices "." and ".." in trees' ' - ( - git init dots && - cd dots && - blob=$(echo foo | git hash-object -w --stdin) && - tab=$(printf "\\t") && - git mktree <<-EOF && - 100644 blob $blob$tab. - 100644 blob $blob$tab.. - EOF - git fsck 2>out && - cat out && - grep "warning.*\\." out - ) - ' - - test_expect_success 'fsck notices ".git" in trees' ' - ( - git init dotgit && - cd dotgit && - blob=$(echo foo | git hash-object -w --stdin) && - tab=$(printf "\\t") && - git mktree <<-EOF && - 100644 blob $blob$tab.git - EOF - git fsck 2>out && - cat out && - grep "warning.*\\.git" out - ) - ' + while read name path pretty; do + while read mode type; do + : ${pretty:=$path} + test_expect_success "fsck notices $pretty as $type" ' + ( + git init $name-$type && + cd $name-$type && + echo content >file && + git add file && + git commit -m base && + blob=$(git rev-parse :file) && + tree=$(git rev-parse HEAD^{tree}) && + value=$(eval "echo \$$type") && + printf "$mode $type %s\t%s" "$value" "$path" >bad && + bad_tree=$(git mktree out && + cat out && + grep "warning.*tree $bad_tree" out + )' + done <<-\EOF + 100644 blob + 040000 tree + EOF + done <<-EOF + dot . + dotdot .. + dotgit .git + dotgit-case .GIT + dotgit-unicode .gI${u200c}T .gI{u200c}T + dotgit-case2 .Git + git-tilde1 git~1 + dotgitdot .git. + dot-backslash-case .\\\\.GIT\\\\foobar + dotgit-case-backslash .git\\\\foobar + EOF # create a static test repo which is broken by omitting # one particular object ($1, which is looked up via rev-parse diff --combined t/test-lib.sh index cf19339cce,a7a4639d7c..79e8a33d04 --- a/t/test-lib.sh +++ b/t/test-lib.sh @@@ -169,7 -169,11 +169,11 @@@ _z40=0000000000000000000000000000000000 LF=' ' - export _x05 _x40 _z40 LF + # UTF-8 ZERO WIDTH NON-JOINER, which HFS+ ignores + # when case-folding filenames + u200c=$(printf '\342\200\214') + + export _x05 _x40 _z40 LF u200c # Each test should start with something like this, after copyright notices: # @@@ -233,10 -237,6 +237,10 @@@ d --root=*) root=$(expr "z$1" : 'z[^=]*=\(.*\)') shift ;; + -x) + trace=t + verbose=t + shift ;; *) echo "error: unknown test option '$1'" >&2; exit 1 ;; esac @@@ -521,39 -521,10 +525,39 @@@ maybe_setup_valgrind () fi } +# This is a separate function because some tests use +# "return" to end a test_expect_success block early +# (and we want to make sure we run any cleanup like +# "set +x"). +test_eval_inner_ () { + # Do not add anything extra (including LF) after '$*' + eval " + test \"$trace\" = t && set -x + $*" +} + test_eval_ () { - # This is a separate function because some tests use - # "return" to end a test_expect_success block early. - eval &3 2>&4 "$*" + # We run this block with stderr redirected to avoid extra cruft + # during a "-x" trace. Once in "set -x" mode, we cannot prevent + # the shell from printing the "set +x" to turn it off (nor the saving + # of $? before that). But we can make sure that the output goes to + # /dev/null. + # + # The test itself is run with stderr put back to &4 (so either to + # /dev/null, or to the original stderr if --verbose was used). + { + test_eval_inner_ "$@" &3 2>&4 + test_eval_ret_=$? + if test "$trace" = t + then + set +x + if test "$test_eval_ret_" != 0 + then + say_color error >&4 "error: last command exited with \$?=$test_eval_ret_" + fi + fi + } 2>/dev/null + return $test_eval_ret_ } test_run_ () { @@@ -564,8 -535,7 +568,8 @@@ eval_ret=$? teardown_malloc_check - if test -z "$immediate" || test $eval_ret = 0 || test -n "$expecting_failure" + if test -z "$immediate" || test $eval_ret = 0 || + test -n "$expecting_failure" && test "$test_cleanup" != ":" then setup_malloc_check test_eval_ "$test_cleanup" @@@ -847,8 -817,7 +851,8 @@@ rm -fr "$TRASH_DIRECTORY" || } HOME="$TRASH_DIRECTORY" -export HOME +GNUPGHOME="$HOME/gnupg-home-not-used" +export HOME GNUPGHOME if test -z "$TEST_NO_CREATE_REPO" then @@@ -905,7 -874,7 +909,7 @@@ case $(uname -s) i # backslashes in pathspec are converted to '/' # exec does not inherit the PID test_set_prereq MINGW - test_set_prereq NOT_CYGWIN + test_set_prereq NATIVE_CRLF test_set_prereq SED_STRIPS_CR test_set_prereq GREP_STRIPS_CR GIT_TEST_CMP=mingw_test_cmp @@@ -913,6 -882,7 +917,6 @@@ *CYGWIN*) test_set_prereq POSIXPERM test_set_prereq EXECKEEPSPID - test_set_prereq NOT_MINGW test_set_prereq CYGWIN test_set_prereq SED_STRIPS_CR test_set_prereq GREP_STRIPS_CR @@@ -921,6 -891,8 +925,6 @@@ test_set_prereq POSIXPERM test_set_prereq BSLASHPSPEC test_set_prereq EXECKEEPSPID - test_set_prereq NOT_MINGW - test_set_prereq NOT_CYGWIN ;; esac diff --combined utf8.c index 4541777949,3b77e97658..9a3f4ad232 --- a/utf8.c +++ b/utf8.c @@@ -239,6 -239,13 +239,6 @@@ int is_utf8(const char *text return 1; } -static void strbuf_addchars(struct strbuf *sb, int c, size_t n) -{ - strbuf_grow(sb, n); - memset(sb->buf + sb->len, c, n); - strbuf_setlen(sb, sb->len + n); -} - static void strbuf_add_indented_text(struct strbuf *buf, const char *text, int indent, int indent2) { @@@ -375,9 -382,6 +375,9 @@@ void strbuf_utf8_replace(struct strbuf dst += n; } + if (src >= end) + break; + old = src; n = utf8_width((const char**)&src, NULL); if (!src) /* broken utf-8, do nothing */ @@@ -561,3 -565,67 +561,67 @@@ int mbs_chrlen(const char **text, size_ return chrlen; } + + /* + * Pick the next char from the stream, folding as an HFS+ filename comparison + * would. Note that this is _not_ complete by any means. It's just enough + * to make is_hfs_dotgit() work, and should not be used otherwise. + */ + static ucs_char_t next_hfs_char(const char **in) + { + while (1) { + ucs_char_t out = pick_one_utf8_char(in, NULL); + /* + * check for malformed utf8. Technically this + * gets converted to a percent-sequence, but + * returning 0 is good enough for is_hfs_dotgit + * to realize it cannot be .git + */ + if (!*in) + return 0; + + /* these code points are ignored completely */ + switch (out) { + case 0x200c: /* ZERO WIDTH NON-JOINER */ + case 0x200d: /* ZERO WIDTH JOINER */ + case 0x200e: /* LEFT-TO-RIGHT MARK */ + case 0x200f: /* RIGHT-TO-LEFT MARK */ + case 0x202a: /* LEFT-TO-RIGHT EMBEDDING */ + case 0x202b: /* RIGHT-TO-LEFT EMBEDDING */ + case 0x202c: /* POP DIRECTIONAL FORMATTING */ + case 0x202d: /* LEFT-TO-RIGHT OVERRIDE */ + case 0x202e: /* RIGHT-TO-LEFT OVERRIDE */ + case 0x206a: /* INHIBIT SYMMETRIC SWAPPING */ + case 0x206b: /* ACTIVATE SYMMETRIC SWAPPING */ + case 0x206c: /* INHIBIT ARABIC FORM SHAPING */ + case 0x206d: /* ACTIVATE ARABIC FORM SHAPING */ + case 0x206e: /* NATIONAL DIGIT SHAPES */ + case 0x206f: /* NOMINAL DIGIT SHAPES */ + case 0xfeff: /* ZERO WIDTH NO-BREAK SPACE */ + continue; + } + + /* + * there's a great deal of other case-folding that occurs, + * but this is enough to catch anything that will convert + * to ".git" + */ + return tolower(out); + } + } + + int is_hfs_dotgit(const char *path) + { + ucs_char_t c; + + if (next_hfs_char(&path) != '.' || + next_hfs_char(&path) != 'g' || + next_hfs_char(&path) != 'i' || + next_hfs_char(&path) != 't') + return 0; + c = next_hfs_char(&path); + if (c && !is_dir_sep(c)) + return 0; + + return 1; + }