/* * Basically keep a cache of X->Y so that we can repeatedly replace * the same anonymized string with another. The actual generation * is farmed out to the generate function. */ static const void *anonymize_mem(struct hashmap *map, void *(*generate)(const void *, size_t *), const void *orig, size_t *len) { struct anonymized_entry key, *ret; if (!map->cmpfn) hashmap_init(map, anonymized_entry_cmp, 0); hashmap_entry_init(&key, memhash(orig, *len)); key.orig = orig; key.orig_len = *len; ret = hashmap_get(map, &key, NULL); if (!ret) { ret = xmalloc(sizeof(*ret)); hashmap_entry_init(&ret->hash, key.hash.hash); ret->orig = xstrdup(orig); ret->orig_len = *len; ret->anon = generate(orig, len); ret->anon_len = *len; hashmap_put(map, ret); } *len = ret->anon_len; return ret->anon; }
void _isvn_commitdrain_add(unsigned rev, int incr) { struct fetchdone_range *newr, *exist, key; key.r_lo = rev; hashmap_entry_init(&key.r_entry, memhash(&key.r_lo, sizeof(key.r_lo))); if (incr > 0) { newr = xmalloc(sizeof(*newr)); newr->r_lo = rev; newr->r_hi = incr; /* reused as refcnt */ hashmap_entry_init(&newr->r_entry, memhash(&newr->r_lo, sizeof(newr->r_lo))); } else newr = NULL; isvn_g_lock(); exist = hashmap_get(&g_commitdrain_hash, &key); if (incr > 0) { if (exist) exist->r_hi += incr; else hashmap_add(&g_commitdrain_hash, newr); } else { int refcnt; /* INVARIANTS */ if (exist == NULL) die("negative refcnt %d (ne)", incr); refcnt = (int)exist->r_hi + incr; /* INVARIANTS */ if (refcnt < 0) die("negative refcnt %d", refcnt); if (refcnt > 0) exist->r_hi = refcnt; else { hashmap_remove(&g_commitdrain_hash, exist); /* free it below */ newr = exist; } } isvn_g_unlock(); if (exist && newr) free(newr); }
/* * Test performance of hashmap.[ch] * Usage: time echo "perfhashmap method rounds" | test-hashmap */ static void perf_hashmap(unsigned int method, unsigned int rounds) { struct hashmap map; char buf[16]; struct test_entry **entries; unsigned int *hashes; unsigned int i, j; entries = malloc(TEST_SIZE * sizeof(struct test_entry *)); hashes = malloc(TEST_SIZE * sizeof(int)); for (i = 0; i < TEST_SIZE; i++) { snprintf(buf, sizeof(buf), "%i", i); entries[i] = alloc_test_entry(0, buf, strlen(buf), "", 0); hashes[i] = hash(method, i, entries[i]->key); } if (method & TEST_ADD) { /* test adding to the map */ for (j = 0; j < rounds; j++) { hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); /* add entries */ for (i = 0; i < TEST_SIZE; i++) { hashmap_entry_init(entries[i], hashes[i]); hashmap_add(&map, entries[i]); } hashmap_free(&map, 0); } } else { /* test map lookups */ hashmap_init(&map, (hashmap_cmp_fn) test_entry_cmp, 0); /* fill the map (sparsely if specified) */ j = (method & TEST_SPARSE) ? TEST_SIZE / 10 : TEST_SIZE; for (i = 0; i < j; i++) { hashmap_entry_init(entries[i], hashes[i]); hashmap_add(&map, entries[i]); } for (j = 0; j < rounds; j++) { for (i = 0; i < TEST_SIZE; i++) { hashmap_get_from_hash(&map, hashes[i], entries[i]->key); } } hashmap_free(&map, 0); } }
static struct dir_entry *hash_dir_entry(struct index_state *istate, struct cache_entry *ce, int namelen) { /* * Throw each directory component in the hash for quick lookup * during a git status. Directory components are stored without their * closing slash. Despite submodules being a directory, they never * reach this point, because they are stored * in index_state.name_hash (as ordinary cache_entries). */ struct dir_entry *dir; /* get length of parent directory */ while (namelen > 0 && !is_dir_sep(ce->name[namelen - 1])) namelen--; if (namelen <= 0) return NULL; namelen--; /* lookup existing entry for that directory */ dir = find_dir_entry(istate, ce->name, namelen); if (!dir) { /* not found, create it and add to hash table */ FLEX_ALLOC_MEM(dir, name, ce->name, namelen); hashmap_entry_init(dir, memihash(ce->name, namelen)); dir->namelen = namelen; hashmap_add(&istate->dir_hash, dir); /* recursively add missing parent directories */ dir->parent = hash_dir_entry(istate, ce, namelen); } return dir; }
static struct dir_entry *find_dir_entry(struct index_state *istate, const char *name, unsigned int namelen) { struct dir_entry key; hashmap_entry_init(&key, memihash(name, namelen)); key.namelen = namelen; return hashmap_get(&istate->dir_hash, &key, name); }
/* * Initialize an fsentry structure for use by fsentry_hash and fsentry_cmp. */ static void fsentry_init(struct fsentry *fse, struct fsentry *list, const char *name, size_t len) { fse->list = list; fse->name = name; fse->len = len; hashmap_entry_init(fse, fsentry_hash(fse)); }
struct subprocess_entry *subprocess_find_entry(struct hashmap *hashmap, const char *cmd) { struct subprocess_entry key; hashmap_entry_init(&key, strhash(cmd)); key.cmd = cmd; return hashmap_get(hashmap, &key, NULL); }
static struct map_entry *alloc_entry(int hash, char *key, int klen, char *value, int vlen) { struct map_entry *entry = malloc(sizeof(struct map_entry) + klen + vlen + 2); hashmap_entry_init(entry, hash); memcpy(entry->key, key, klen + 1); memcpy(entry->key + klen + 1, value, vlen + 1); return entry; }
static void cache_add(struct submodule_cache *cache, struct submodule *submodule) { unsigned int hash = hash_oid_string(&submodule->gitmodules_oid, submodule->name); struct submodule_entry *e = xmalloc(sizeof(*e)); hashmap_entry_init(e, hash); e->config = submodule; hashmap_add(&cache->for_name, e); }
void *oidmap_put(struct oidmap *map, void *entry) { struct oidmap_entry *to_put = entry; if (!map->map.cmpfn) oidmap_init(map, 0); hashmap_entry_init(&to_put->internal_entry, hash(&to_put->oid)); return hashmap_put(&map->map, to_put); }
static struct submodule_hash_entry *alloc_submodule_hash_entry( const char *submodule, struct ref_store *refs) { struct submodule_hash_entry *entry; FLEX_ALLOC_STR(entry, submodule, submodule); hashmap_entry_init(entry, strhash(submodule)); entry->refs = refs; return entry; }
void *oidmap_remove(struct oidmap *map, const struct object_id *key) { struct hashmap_entry entry; if (!map->map.cmpfn) oidmap_init(map, 0); hashmap_entry_init(&entry, hash(key)); return hashmap_remove(&map->map, &entry, key); }
static void cache_put_path(struct submodule_cache *cache, struct submodule *submodule) { unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, submodule->path); struct submodule_entry *e = xmalloc(sizeof(*e)); hashmap_entry_init(e, hash); e->config = submodule; hashmap_put(&cache->for_path, e); }
static void changed_files(struct hashmap *result, const char *index_path, const char *workdir) { struct child_process update_index = CHILD_PROCESS_INIT; struct child_process diff_files = CHILD_PROCESS_INIT; struct strbuf index_env = STRBUF_INIT, buf = STRBUF_INIT; const char *git_dir = absolute_path(get_git_dir()), *env[] = { NULL, NULL }; FILE *fp; strbuf_addf(&index_env, "GIT_INDEX_FILE=%s", index_path); env[0] = index_env.buf; argv_array_pushl(&update_index.args, "--git-dir", git_dir, "--work-tree", workdir, "update-index", "--really-refresh", "-q", "--unmerged", NULL); update_index.no_stdin = 1; update_index.no_stdout = 1; update_index.no_stderr = 1; update_index.git_cmd = 1; update_index.use_shell = 0; update_index.clean_on_exit = 1; update_index.dir = workdir; update_index.env = env; /* Ignore any errors of update-index */ run_command(&update_index); argv_array_pushl(&diff_files.args, "--git-dir", git_dir, "--work-tree", workdir, "diff-files", "--name-only", "-z", NULL); diff_files.no_stdin = 1; diff_files.git_cmd = 1; diff_files.use_shell = 0; diff_files.clean_on_exit = 1; diff_files.out = -1; diff_files.dir = workdir; diff_files.env = env; if (start_command(&diff_files)) die("could not obtain raw diff"); fp = xfdopen(diff_files.out, "r"); while (!strbuf_getline_nul(&buf, fp)) { struct path_entry *entry; FLEX_ALLOC_STR(entry, path, buf.buf); hashmap_entry_init(entry, strhash(buf.buf)); hashmap_add(result, entry); } fclose(fp); if (finish_command(&diff_files)) die("diff-files did not exit properly"); strbuf_release(&index_env); strbuf_release(&buf); }
static void hash_index_entry(struct index_state *istate, struct cache_entry *ce) { if (ce->ce_flags & CE_HASHED) return; ce->ce_flags |= CE_HASHED; hashmap_entry_init(ce, memihash(ce->name, ce_namelen(ce))); hashmap_add(&istate->name_hash, ce); if (ignore_case) add_dir_entry(istate, ce); }
static void cache_remove_path(struct submodule_cache *cache, struct submodule *submodule) { unsigned int hash = hash_sha1_string(submodule->gitmodules_sha1, submodule->path); struct submodule_entry e; struct submodule_entry *removed; hashmap_entry_init(&e, hash); e.config = submodule; removed = hashmap_remove(&cache->for_path, &e, NULL); free(removed); }
void isvn_mark_fetchdone(unsigned revlo, unsigned revhi) { struct fetchdone_range *done, *exist, key; done = xmalloc(sizeof(*done)); if (done == NULL) die("malloc"); isvn_g_lock(); if (g_rev_fetchdone == revlo - 1) { g_rev_fetchdone = revhi; while (true) { key.r_lo = revhi + 1; hashmap_entry_init(&key.r_entry, memhash(&key.r_lo, sizeof(key.r_lo))); exist = hashmap_remove(&g_fetchdone_hash, &key); if (!exist) break; g_rev_fetchdone = revhi = exist->r_hi; free(exist); } cond_broadcast(&g_rev_cond); } else { done->r_lo = revlo; done->r_hi = revhi; hashmap_entry_init(&done->r_entry, memhash(&done->r_lo, sizeof(done->r_lo))); hashmap_add(&g_fetchdone_hash, done); done = NULL; } isvn_g_unlock(); if (done) free(done); }
static int init_patch_id_entry(struct patch_id *patch, struct commit *commit, struct patch_ids *ids) { struct object_id header_only_patch_id; patch->commit = commit; if (commit_patch_id(commit, &ids->diffopts, &header_only_patch_id, 1)) return -1; hashmap_entry_init(patch, sha1hash(header_only_patch_id.hash)); return 0; }
static int init_patch_id_entry(struct patch_id *patch, struct commit *commit, struct patch_ids *ids) { unsigned char header_only_patch_id[GIT_SHA1_RAWSZ]; patch->commit = commit; if (commit_patch_id(commit, &ids->diffopts, header_only_patch_id, 1)) return -1; hashmap_entry_init(patch, sha1hash(header_only_patch_id)); return 0; }
static void find_exact_matches(struct string_list *a, struct string_list *b) { struct hashmap map; int i; hashmap_init(&map, (hashmap_cmp_fn)patch_util_cmp, NULL, 0); /* First, add the patches of a to a hash map */ for (i = 0; i < a->nr; i++) { struct patch_util *util = a->items[i].util; util->i = i; util->patch = a->items[i].string; util->diff = util->patch + util->diff_offset; hashmap_entry_init(util, strhash(util->diff)); hashmap_add(&map, util); } /* Now try to find exact matches in b */ for (i = 0; i < b->nr; i++) { struct patch_util *util = b->items[i].util, *other; util->i = i; util->patch = b->items[i].string; util->diff = util->patch + util->diff_offset; hashmap_entry_init(util, strhash(util->diff)); other = hashmap_remove(&map, util, NULL); if (other) { if (other->matching >= 0) BUG("already assigned!"); other->matching = i; util->matching = other->i; } } hashmap_free(&map, 0); }
static void *lazy_name_thread_proc(void *_data) { struct lazy_name_thread_data *d = _data; int k; for (k = 0; k < d->istate->cache_nr; k++) { struct cache_entry *ce_k = d->istate->cache[k]; ce_k->ce_flags |= CE_HASHED; hashmap_entry_init(ce_k, d->lazy_entries[k].hash_name); hashmap_add(&d->istate->name_hash, ce_k); } return NULL; }
/* * Retrieve the 'value' stored in a hashmap given the provided 'key'. * If there is no matching entry, return NULL. */ static void *attr_hashmap_get(struct attr_hashmap *map, const char *key, size_t keylen) { struct attr_hash_entry k; struct attr_hash_entry *e; if (!map->map.tablesize) attr_hashmap_init(map); hashmap_entry_init(&k, memhash(key, keylen)); k.key = key; k.keylen = keylen; e = hashmap_get(&map->map, &k, NULL); return e ? e->value : NULL; }
static struct dir_entry *hash_dir_entry_with_parent_and_prefix( struct index_state *istate, struct dir_entry *parent, struct strbuf *prefix) { struct dir_entry *dir; unsigned int hash; int lock_nr; /* * Either we have a parent directory and path with slash(es) * or the directory is an immediate child of the root directory. */ assert((parent != NULL) ^ (strchr(prefix->buf, '/') == NULL)); if (parent) hash = memihash_cont(parent->ent.hash, prefix->buf + parent->namelen, prefix->len - parent->namelen); else hash = memihash(prefix->buf, prefix->len); lock_nr = compute_dir_lock_nr(&istate->dir_hash, hash); lock_dir_mutex(lock_nr); dir = find_dir_entry__hash(istate, prefix->buf, prefix->len, hash); if (!dir) { FLEX_ALLOC_MEM(dir, name, prefix->buf, prefix->len); hashmap_entry_init(dir, hash); dir->namelen = prefix->len; dir->parent = parent; hashmap_add(&istate->dir_hash, dir); if (parent) { unlock_dir_mutex(lock_nr); /* All I really need here is an InterlockedIncrement(&(parent->nr)) */ lock_nr = compute_dir_lock_nr(&istate->dir_hash, parent->ent.hash); lock_dir_mutex(lock_nr); parent->nr++; } } unlock_dir_mutex(lock_nr); return dir; }
static void add_left_or_right(struct hashmap *map, const char *path, const char *content, int is_right) { struct pair_entry *e, *existing; FLEX_ALLOC_STR(e, path, path); hashmap_entry_init(e, strhash(path)); existing = hashmap_get(map, e, NULL); if (existing) { free(e); e = existing; } else { e->left[0] = e->right[0] = '\0'; hashmap_add(map, e); } strlcpy(is_right ? e->right : e->left, content, PATH_MAX); }
/* Add 'value' to a hashmap based on the provided 'key'. */ static void attr_hashmap_add(struct attr_hashmap *map, const char *key, size_t keylen, void *value) { struct attr_hash_entry *e; if (!map->map.tablesize) attr_hashmap_init(map); e = xmalloc(sizeof(struct attr_hash_entry)); hashmap_entry_init(e, memhash(key, keylen)); e->key = key; e->keylen = keylen; e->value = value; hashmap_add(&map->map, e); }
static struct submodule *cache_lookup_name(struct submodule_cache *cache, const unsigned char *gitmodules_sha1, const char *name) { struct submodule_entry *entry; unsigned int hash = hash_sha1_string(gitmodules_sha1, name); struct submodule_entry key; struct submodule key_config; hashcpy(key_config.gitmodules_sha1, gitmodules_sha1); key_config.name = name; hashmap_entry_init(&key, hash); key.config = &key_config; entry = hashmap_get(&cache->for_name, &key, NULL); if (entry) return entry->config; return NULL; }
static struct submodule *cache_lookup_name(struct submodule_cache *cache, const struct object_id *gitmodules_oid, const char *name) { struct submodule_entry *entry; unsigned int hash = hash_oid_string(gitmodules_oid, name); struct submodule_entry key; struct submodule key_config; oidcpy(&key_config.gitmodules_oid, gitmodules_oid); key_config.name = name; hashmap_entry_init(&key, hash); key.config = &key_config; entry = hashmap_get(&cache->for_name, &key, NULL); if (entry) return entry->config; return NULL; }
static void add_to_known_names(const char *path, const unsigned char *peeled, int prio, const unsigned char *sha1) { struct commit_name *e = find_commit_name(peeled); struct tag *tag = NULL; if (replace_name(e, prio, sha1, &tag)) { if (!e) { e = xmalloc(sizeof(struct commit_name)); hashcpy(e->peeled, peeled); hashmap_entry_init(e, sha1hash(peeled)); hashmap_add(&names, e); e->path = NULL; } e->tag = tag; e->prio = prio; e->name_checked = 0; hashcpy(e->sha1, sha1); free(e->path); e->path = xstrdup(path); } }
int subprocess_start(struct hashmap *hashmap, struct subprocess_entry *entry, const char *cmd, subprocess_start_fn startfn) { int err; struct child_process *process; const char *argv[] = { cmd, NULL }; entry->cmd = cmd; process = &entry->process; child_process_init(process); process->argv = argv; process->use_shell = 1; process->in = -1; process->out = -1; process->clean_on_exit = 1; process->clean_on_exit_handler = subprocess_exit_handler; err = start_command(process); if (err) { error("cannot fork to run subprocess '%s'", cmd); return err; } hashmap_entry_init(entry, strhash(cmd)); err = startfn(entry); if (err) { error("initialization for subprocess '%s' failed", cmd); subprocess_stop(hashmap, entry); return err; } hashmap_add(hashmap, entry); return 0; }
static int run_dir_diff(const char *extcmd, int symlinks, const char *prefix, int argc, const char **argv) { char tmpdir[PATH_MAX]; struct strbuf info = STRBUF_INIT, lpath = STRBUF_INIT; struct strbuf rpath = STRBUF_INIT, buf = STRBUF_INIT; struct strbuf ldir = STRBUF_INIT, rdir = STRBUF_INIT; struct strbuf wtdir = STRBUF_INIT; char *lbase_dir, *rbase_dir; size_t ldir_len, rdir_len, wtdir_len; const char *workdir, *tmp; int ret = 0, i; FILE *fp; struct hashmap working_tree_dups, submodules, symlinks2; struct hashmap_iter iter; struct pair_entry *entry; struct index_state wtindex; struct checkout lstate, rstate; int rc, flags = RUN_GIT_CMD, err = 0; struct child_process child = CHILD_PROCESS_INIT; const char *helper_argv[] = { "difftool--helper", NULL, NULL, NULL }; struct hashmap wt_modified, tmp_modified; int indices_loaded = 0; workdir = get_git_work_tree(); /* Setup temp directories */ tmp = getenv("TMPDIR"); xsnprintf(tmpdir, sizeof(tmpdir), "%s/git-difftool.XXXXXX", tmp ? tmp : "/tmp"); if (!mkdtemp(tmpdir)) return error("could not create '%s'", tmpdir); strbuf_addf(&ldir, "%s/left/", tmpdir); strbuf_addf(&rdir, "%s/right/", tmpdir); strbuf_addstr(&wtdir, workdir); if (!wtdir.len || !is_dir_sep(wtdir.buf[wtdir.len - 1])) strbuf_addch(&wtdir, '/'); mkdir(ldir.buf, 0700); mkdir(rdir.buf, 0700); memset(&wtindex, 0, sizeof(wtindex)); memset(&lstate, 0, sizeof(lstate)); lstate.base_dir = lbase_dir = xstrdup(ldir.buf); lstate.base_dir_len = ldir.len; lstate.force = 1; memset(&rstate, 0, sizeof(rstate)); rstate.base_dir = rbase_dir = xstrdup(rdir.buf); rstate.base_dir_len = rdir.len; rstate.force = 1; ldir_len = ldir.len; rdir_len = rdir.len; wtdir_len = wtdir.len; hashmap_init(&working_tree_dups, (hashmap_cmp_fn)working_tree_entry_cmp, NULL, 0); hashmap_init(&submodules, (hashmap_cmp_fn)pair_cmp, NULL, 0); hashmap_init(&symlinks2, (hashmap_cmp_fn)pair_cmp, NULL, 0); child.no_stdin = 1; child.git_cmd = 1; child.use_shell = 0; child.clean_on_exit = 1; child.dir = prefix; child.out = -1; argv_array_pushl(&child.args, "diff", "--raw", "--no-abbrev", "-z", NULL); for (i = 0; i < argc; i++) argv_array_push(&child.args, argv[i]); if (start_command(&child)) die("could not obtain raw diff"); fp = xfdopen(child.out, "r"); /* Build index info for left and right sides of the diff */ i = 0; while (!strbuf_getline_nul(&info, fp)) { int lmode, rmode; struct object_id loid, roid; char status; const char *src_path, *dst_path; if (starts_with(info.buf, "::")) die(N_("combined diff formats('-c' and '--cc') are " "not supported in\n" "directory diff mode('-d' and '--dir-diff').")); if (parse_index_info(info.buf, &lmode, &rmode, &loid, &roid, &status)) break; if (strbuf_getline_nul(&lpath, fp)) break; src_path = lpath.buf; i++; if (status != 'C' && status != 'R') { dst_path = src_path; } else { if (strbuf_getline_nul(&rpath, fp)) break; dst_path = rpath.buf; } if (S_ISGITLINK(lmode) || S_ISGITLINK(rmode)) { strbuf_reset(&buf); strbuf_addf(&buf, "Subproject commit %s", oid_to_hex(&loid)); add_left_or_right(&submodules, src_path, buf.buf, 0); strbuf_reset(&buf); strbuf_addf(&buf, "Subproject commit %s", oid_to_hex(&roid)); if (!oidcmp(&loid, &roid)) strbuf_addstr(&buf, "-dirty"); add_left_or_right(&submodules, dst_path, buf.buf, 1); continue; } if (S_ISLNK(lmode)) { char *content = get_symlink(&loid, src_path); add_left_or_right(&symlinks2, src_path, content, 0); free(content); } if (S_ISLNK(rmode)) { char *content = get_symlink(&roid, dst_path); add_left_or_right(&symlinks2, dst_path, content, 1); free(content); } if (lmode && status != 'C') { if (checkout_path(lmode, &loid, src_path, &lstate)) { ret = error("could not write '%s'", src_path); goto finish; } } if (rmode && !S_ISLNK(rmode)) { struct working_tree_entry *entry; /* Avoid duplicate working_tree entries */ FLEX_ALLOC_STR(entry, path, dst_path); hashmap_entry_init(entry, strhash(dst_path)); if (hashmap_get(&working_tree_dups, entry, NULL)) { free(entry); continue; } hashmap_add(&working_tree_dups, entry); if (!use_wt_file(workdir, dst_path, &roid)) { if (checkout_path(rmode, &roid, dst_path, &rstate)) { ret = error("could not write '%s'", dst_path); goto finish; } } else if (!is_null_oid(&roid)) { /* * Changes in the working tree need special * treatment since they are not part of the * index. */ struct cache_entry *ce2 = make_cache_entry(rmode, roid.hash, dst_path, 0, 0); add_index_entry(&wtindex, ce2, ADD_CACHE_JUST_APPEND); add_path(&rdir, rdir_len, dst_path); if (ensure_leading_directories(rdir.buf)) { ret = error("could not create " "directory for '%s'", dst_path); goto finish; } add_path(&wtdir, wtdir_len, dst_path); if (symlinks) { if (symlink(wtdir.buf, rdir.buf)) { ret = error_errno("could not symlink '%s' to '%s'", wtdir.buf, rdir.buf); goto finish; } } else { struct stat st; if (stat(wtdir.buf, &st)) st.st_mode = 0644; if (copy_file(rdir.buf, wtdir.buf, st.st_mode)) { ret = error("could not copy '%s' to '%s'", wtdir.buf, rdir.buf); goto finish; } } } } } fclose(fp); fp = NULL; if (finish_command(&child)) { ret = error("error occurred running diff --raw"); goto finish; } if (!i) goto finish; /* * Changes to submodules require special treatment.This loop writes a * temporary file to both the left and right directories to show the * change in the recorded SHA1 for the submodule. */ hashmap_iter_init(&submodules, &iter); while ((entry = hashmap_iter_next(&iter))) { if (*entry->left) { add_path(&ldir, ldir_len, entry->path); ensure_leading_directories(ldir.buf); write_file(ldir.buf, "%s", entry->left); } if (*entry->right) { add_path(&rdir, rdir_len, entry->path); ensure_leading_directories(rdir.buf); write_file(rdir.buf, "%s", entry->right); } } /* * Symbolic links require special treatment.The standard "git diff" * shows only the link itself, not the contents of the link target. * This loop replicates that behavior. */ hashmap_iter_init(&symlinks2, &iter); while ((entry = hashmap_iter_next(&iter))) { if (*entry->left) { add_path(&ldir, ldir_len, entry->path); ensure_leading_directories(ldir.buf); write_file(ldir.buf, "%s", entry->left); } if (*entry->right) { add_path(&rdir, rdir_len, entry->path); ensure_leading_directories(rdir.buf); write_file(rdir.buf, "%s", entry->right); } } strbuf_release(&buf); strbuf_setlen(&ldir, ldir_len); helper_argv[1] = ldir.buf; strbuf_setlen(&rdir, rdir_len); helper_argv[2] = rdir.buf; if (extcmd) { helper_argv[0] = extcmd; flags = 0; } else setenv("GIT_DIFFTOOL_DIRDIFF", "true", 1); rc = run_command_v_opt(helper_argv, flags); /* * If the diff includes working copy files and those * files were modified during the diff, then the changes * should be copied back to the working tree. * Do not copy back files when symlinks are used and the * external tool did not replace the original link with a file. * * These hashes are loaded lazily since they aren't needed * in the common case of --symlinks and the difftool updating * files through the symlink. */ hashmap_init(&wt_modified, (hashmap_cmp_fn)path_entry_cmp, NULL, wtindex.cache_nr); hashmap_init(&tmp_modified, (hashmap_cmp_fn)path_entry_cmp, NULL, wtindex.cache_nr); for (i = 0; i < wtindex.cache_nr; i++) { struct hashmap_entry dummy; const char *name = wtindex.cache[i]->name; struct stat st; add_path(&rdir, rdir_len, name); if (lstat(rdir.buf, &st)) continue; if ((symlinks && S_ISLNK(st.st_mode)) || !S_ISREG(st.st_mode)) continue; if (!indices_loaded) { static struct lock_file lock; strbuf_reset(&buf); strbuf_addf(&buf, "%s/wtindex", tmpdir); if (hold_lock_file_for_update(&lock, buf.buf, 0) < 0 || write_locked_index(&wtindex, &lock, COMMIT_LOCK)) { ret = error("could not write %s", buf.buf); rollback_lock_file(&lock); goto finish; } changed_files(&wt_modified, buf.buf, workdir); strbuf_setlen(&rdir, rdir_len); changed_files(&tmp_modified, buf.buf, rdir.buf); add_path(&rdir, rdir_len, name); indices_loaded = 1; } hashmap_entry_init(&dummy, strhash(name)); if (hashmap_get(&tmp_modified, &dummy, name)) { add_path(&wtdir, wtdir_len, name); if (hashmap_get(&wt_modified, &dummy, name)) { warning(_("both files modified: '%s' and '%s'."), wtdir.buf, rdir.buf); warning(_("working tree file has been left.")); warning("%s", ""); err = 1; } else if (unlink(wtdir.buf) || copy_file(wtdir.buf, rdir.buf, st.st_mode)) warning_errno(_("could not copy '%s' to '%s'"), rdir.buf, wtdir.buf); } } if (err) { warning(_("temporary files exist in '%s'."), tmpdir); warning(_("you may want to cleanup or recover these.")); exit(1); } else exit_cleanup(tmpdir, rc); finish: if (fp) fclose(fp); free(lbase_dir); free(rbase_dir); strbuf_release(&ldir); strbuf_release(&rdir); strbuf_release(&wtdir); strbuf_release(&buf); return ret; }