untracked cache: guard and disable on system changes

If the user enables untracked cache, then - move worktree to an unsupported filesystem - or simply upgrade OS - or move the whole (portable) disk from one machine to another - or access a shared fs from another machine there's no guarantee that untracked cache can still function properly. Record the worktree location and OS footprint in the cache. If it changes, err on the safe side and disable the cache. The user can 'update-index --untracked-cache' again to make sure all conditions are met. This adds a new requirement that setup_git_directory* must be called before read_cache() because we need worktree location by then, or the cache is dropped. This change does not cover all bases, you can fool it if you try hard. The point is to stop accidents. Helped-by: Eric Sunshine <sunshine@sunshineco.com> Helped-by: brian m. carlson <sandals@crustytoothpaste.net> Helped-by: Torsten Bögershausen <tboegi@web.de> Signed-off-by: Nguyễn Thái Ngọc Duy <pclouds@gmail.com> Signed-off-by: Junio C Hamano <gitster@pobox.com>

Nguyễn Thái Ngọc Duy committed Mar 8, 2015 at 17:12 UTC 1e8fef609e78110e276df633c5ba1fb1f1589fa5
6 files changed +72 -7
Documentation/technical/index-format.txt
+4
@@ -242,6 +242,10 @@ Git index format
242
243 The extension starts with
244
245 + - A sequence of NUL-terminated strings, preceded by the size of the
246 + sequence in variable width encoding. Each string describes the
247 + environment where the cache can be used.
248 +
249 - Stat data of $GIT_DIR/info/exclude. See "Index entry" section from
250 ctime field until "file size".
251
builtin/update-index.c
+10 -6
@@ -1104,7 +1104,7 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
1104 the_index.split_index = NULL;
1105 the_index.cache_changed |= SOMETHING_CHANGED;
1106 }
1107 - if (untracked_cache > 0 && !the_index.untracked) {
1107 + if (untracked_cache > 0) {
1108 struct untracked_cache *uc;
1109
1110 if (untracked_cache < 2) {
@@ -1112,11 +1112,15 @@ int cmd_update_index(int argc, const char **argv, const char *prefix)
1112 if (!test_if_untracked_cache_is_supported())
1113 return 1;
1114 }
1115 - uc = xcalloc(1, sizeof(*uc));
1116 - uc->exclude_per_dir = ".gitignore";
1117 - /* should be the same flags used by git-status */
1118 - uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
1119 - the_index.untracked = uc;
1115 + if (!the_index.untracked) {
1116 + uc = xcalloc(1, sizeof(*uc));
1117 + strbuf_init(&uc->ident, 100);
1118 + uc->exclude_per_dir = ".gitignore";
1119 + /* should be the same flags used by git-status */
1120 + uc->dir_flags = DIR_SHOW_OTHER_DIRECTORIES | DIR_HIDE_EMPTY_DIRECTORIES;
1121 + the_index.untracked = uc;
1122 + }
1123 + add_untracked_ident(the_index.untracked);
1124 the_index.cache_changed |= UNTRACKED_CHANGED;
1125 } else if (!untracked_cache && the_index.untracked) {
1126 the_index.untracked = NULL;
dir.c
+54 -1
@@ -1794,6 +1794,40 @@ static int treat_leading_path(struct dir_struct *dir,
1794 return rc;
1795 }
1796
1797 +static const char *get_ident_string(void)
1798 +{
1799 + static struct strbuf sb = STRBUF_INIT;
1800 + struct utsname uts;
1801 +
1802 + if (sb.len)
1803 + return sb.buf;
1804 + if (uname(&uts))
1805 + die_errno(_("failed to get kernel name and information"));
1806 + strbuf_addf(&sb, "Location %s, system %s %s %s", get_git_work_tree(),
1807 + uts.sysname, uts.release, uts.version);
1808 + return sb.buf;
1809 +}
1810 +
1811 +static int ident_in_untracked(const struct untracked_cache *uc)
1812 +{
1813 + const char *end = uc->ident.buf + uc->ident.len;
1814 + const char *p = uc->ident.buf;
1815 +
1816 + for (p = uc->ident.buf; p < end; p += strlen(p) + 1)
1817 + if (!strcmp(p, get_ident_string()))
1818 + return 1;
1819 + return 0;
1820 +}
1821 +
1822 +void add_untracked_ident(struct untracked_cache *uc)
1823 +{
1824 + if (ident_in_untracked(uc))
1825 + return;
1826 + strbuf_addstr(&uc->ident, get_ident_string());
1827 + /* this strbuf contains a list of strings, save NUL too */
1828 + strbuf_addch(&uc->ident, 0);
1829 +}
1830 +
1831 static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *dir,
1832 int base_len,
1833 const struct pathspec *pathspec)
@@ -1860,6 +1894,11 @@ static struct untracked_cache_dir *validate_untracked_cache(struct dir_struct *d
1894 if (ce_skip_worktree(active_cache[i]))
1895 return NULL;
1896
1897 + if (!ident_in_untracked(dir->untracked)) {
1898 + warning(_("Untracked cache is disabled on this system."));
1899 + return NULL;
1900 + }
1901 +
1902 if (!dir->untracked->root) {
1903 const int len = sizeof(*dir->untracked->root);
1904 dir->untracked->root = xmalloc(len);
@@ -2268,6 +2307,11 @@ void write_untracked_extension(struct strbuf *out, struct untracked_cache *untra
2307 hashcpy(ouc->excludes_file_sha1, untracked->ss_excludes_file.sha1);
2308 ouc->dir_flags = htonl(untracked->dir_flags);
2309 memcpy(ouc->exclude_per_dir, untracked->exclude_per_dir, len + 1);
2310 +
2311 + varint_len = encode_varint(untracked->ident.len, varbuf);
2312 + strbuf_add(out, varbuf, varint_len);
2313 + strbuf_add(out, untracked->ident.buf, untracked->ident.len);
2314 +
2315 strbuf_add(out, ouc, ouc_size(len));
2316 free(ouc);
2317 ouc = NULL;
@@ -2453,17 +2497,26 @@ struct untracked_cache *read_untracked_extension(const void *data, unsigned long
2497 struct untracked_cache *uc;
2498 struct read_data rd;
2499 const unsigned char *next = data, *end = (const unsigned char *)data + sz;
2456 - int len;
2500 + const char *ident;
2501 + int ident_len, len;
2502
2503 if (sz <= 1 || end[-1] != '\0')
2504 return NULL;
2505 end--;
2506
2507 + ident_len = decode_varint(&next);
2508 + if (next + ident_len > end)
2509 + return NULL;
2510 + ident = (const char *)next;
2511 + next += ident_len;
2512 +
2513 ouc = (const struct ondisk_untracked_cache *)next;
2514 if (next + ouc_size(0) > end)
2515 return NULL;
2516
2517 uc = xcalloc(1, sizeof(*uc));
2518 + strbuf_init(&uc->ident, ident_len);
2519 + strbuf_add(&uc->ident, ident, ident_len);
2520 load_sha1_stat(&uc->ss_info_exclude, &ouc->info_exclude_stat,
2521 ouc->info_exclude_sha1);
2522 load_sha1_stat(&uc->ss_excludes_file, &ouc->excludes_file_stat,
dir.h
+2
@@ -127,6 +127,7 @@ struct untracked_cache {
127 struct sha1_stat ss_info_exclude;
128 struct sha1_stat ss_excludes_file;
129 const char *exclude_per_dir;
130 + struct strbuf ident;
131 /*
132 * dir_struct#flags must match dir_flags or the untracked
133 * cache is ignored.
@@ -305,4 +306,5 @@ void untracked_cache_add_to_index(struct index_state *, const char *);
306 void free_untracked_cache(struct untracked_cache *);
307 struct untracked_cache *read_untracked_extension(const void *data, unsigned long sz);
308 void write_untracked_extension(struct strbuf *out, struct untracked_cache *untracked);
309 +void add_untracked_ident(struct untracked_cache *);
310 #endif
git-compat-util.h
+1
@@ -134,6 +134,7 @@
134 #elif defined(_MSC_VER)
135 #include "compat/msvc.h"
136 #else
137 +#include <sys/utsname.h>
138 #include <sys/wait.h>
139 #include <sys/resource.h>
140 #include <sys/socket.h>
test-dump-untracked-cache.c
+1
@@ -44,6 +44,7 @@ int main(int ac, char **av)
44 {
45 struct untracked_cache *uc;
46 struct strbuf base = STRBUF_INIT;
47 + setup_git_directory();
48 if (read_cache() < 0)
49 die("unable to read index file");
50 uc = the_index.untracked;