diff-no-index.c 6.9 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
/*
 * "diff --no-index" support
 * Copyright (c) 2007 by Johannes Schindelin
 * Copyright (c) 2008 by Junio C Hamano
 */

#include "cache.h"
#include "color.h"
#include "commit.h"
#include "blob.h"
#include "tag.h"
#include "diff.h"
#include "diffcore.h"
#include "revision.h"
#include "log-tree.h"
#include "builtin.h"
17
#include "string-list.h"
18
#include "dir.h"
19

20
static int read_directory_contents(const char *path, struct string_list *list)
21 22 23 24 25 26 27 28
{
	DIR *dir;
	struct dirent *e;

	if (!(dir = opendir(path)))
		return error("Could not open directory %s", path);

	while ((e = readdir(dir)))
29
		if (!is_dot_or_dotdot(e->d_name))
30
			string_list_insert(list, e->d_name);
31 32 33 34 35

	closedir(dir);
	return 0;
}

36 37 38 39 40 41 42
/*
 * This should be "(standard input)" or something, but it will
 * probably expose many more breakages in the way no-index code
 * is bolted onto the diff callchain.
 */
static const char file_from_standard_input[] = "-";

43 44 45 46 47 48
static int get_mode(const char *path, int *mode)
{
	struct stat st;

	if (!path || !strcmp(path, "/dev/null"))
		*mode = 0;
49
#ifdef GIT_WINDOWS_NATIVE
50 51 52
	else if (!strcasecmp(path, "nul"))
		*mode = 0;
#endif
53
	else if (path == file_from_standard_input)
54
		*mode = create_ce_mode(0666);
55
	else if (lstat(path, &st))
56 57 58 59 60 61
		return error("Could not access '%s'", path);
	else
		*mode = st.st_mode;
	return 0;
}

62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85
static int populate_from_stdin(struct diff_filespec *s)
{
	struct strbuf buf = STRBUF_INIT;
	size_t size = 0;

	if (strbuf_read(&buf, 0, 0) < 0)
		return error("error while reading from stdin %s",
				     strerror(errno));

	s->should_munmap = 0;
	s->data = strbuf_detach(&buf, &size);
	s->size = size;
	s->should_free = 1;
	s->is_stdin = 1;
	return 0;
}

static struct diff_filespec *noindex_filespec(const char *name, int mode)
{
	struct diff_filespec *s;

	if (!name)
		name = "/dev/null";
	s = alloc_filespec(name);
86
	fill_filespec(s, null_sha1, 0, mode);
87 88 89 90 91
	if (name == file_from_standard_input)
		populate_from_stdin(s);
	return s;
}

92
static int queue_diff(struct diff_options *o,
93
		      const char *name1, const char *name2)
94 95 96 97 98 99
{
	int mode1 = 0, mode2 = 0;

	if (get_mode(name1, &mode1) || get_mode(name2, &mode2))
		return -1;

100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120
	if (mode1 && mode2 && S_ISDIR(mode1) != S_ISDIR(mode2)) {
		struct diff_filespec *d1, *d2;

		if (S_ISDIR(mode1)) {
			/* 2 is file that is created */
			d1 = noindex_filespec(NULL, 0);
			d2 = noindex_filespec(name2, mode2);
			name2 = NULL;
			mode2 = 0;
		} else {
			/* 1 is file that is deleted */
			d1 = noindex_filespec(name1, mode1);
			d2 = noindex_filespec(NULL, 0);
			name1 = NULL;
			mode1 = 0;
		}
		/* emit that file */
		diff_queue(&diff_queued_diff, d1, d2);

		/* and then let the entire directory be created or deleted */
	}
121 122

	if (S_ISDIR(mode1) || S_ISDIR(mode2)) {
123 124
		struct strbuf buffer1 = STRBUF_INIT;
		struct strbuf buffer2 = STRBUF_INIT;
125 126
		struct string_list p1 = STRING_LIST_INIT_DUP;
		struct string_list p2 = STRING_LIST_INIT_DUP;
127
		int i1, i2, ret = 0;
128
		size_t len1 = 0, len2 = 0;
129

130
		if (name1 && read_directory_contents(name1, &p1))
131
			return -1;
132
		if (name2 && read_directory_contents(name2, &p2)) {
133
			string_list_clear(&p1, 0);
134 135 136 137
			return -1;
		}

		if (name1) {
138
			strbuf_addstr(&buffer1, name1);
139
			strbuf_complete(&buffer1, '/');
140
			len1 = buffer1.len;
141 142 143
		}

		if (name2) {
144
			strbuf_addstr(&buffer2, name2);
145
			strbuf_complete(&buffer2, '/');
146
			len2 = buffer2.len;
147 148 149 150 151 152
		}

		for (i1 = i2 = 0; !ret && (i1 < p1.nr || i2 < p2.nr); ) {
			const char *n1, *n2;
			int comp;

153 154 155
			strbuf_setlen(&buffer1, len1);
			strbuf_setlen(&buffer2, len2);

156 157 158 159 160
			if (i1 == p1.nr)
				comp = 1;
			else if (i2 == p2.nr)
				comp = -1;
			else
161
				comp = strcmp(p1.items[i1].string, p2.items[i2].string);
162 163 164 165

			if (comp > 0)
				n1 = NULL;
			else {
166 167
				strbuf_addstr(&buffer1, p1.items[i1++].string);
				n1 = buffer1.buf;
168 169 170 171 172
			}

			if (comp < 0)
				n2 = NULL;
			else {
173 174
				strbuf_addstr(&buffer2, p2.items[i2++].string);
				n2 = buffer2.buf;
175 176 177 178
			}

			ret = queue_diff(o, n1, n2);
		}
179 180
		string_list_clear(&p1, 0);
		string_list_clear(&p2, 0);
181 182
		strbuf_release(&buffer1);
		strbuf_release(&buffer2);
183 184 185 186 187 188 189 190 191 192 193 194

		return ret;
	} else {
		struct diff_filespec *d1, *d2;

		if (DIFF_OPT_TST(o, REVERSE_DIFF)) {
			unsigned tmp;
			const char *tmp_c;
			tmp = mode1; mode1 = mode2; mode2 = tmp;
			tmp_c = name1; name1 = name2; name2 = tmp_c;
		}

195 196
		d1 = noindex_filespec(name1, mode1);
		d2 = noindex_filespec(name2, mode2);
197 198 199 200 201
		diff_queue(&diff_queued_diff, d1, d2);
		return 0;
	}
}

202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238
/* append basename of F to D */
static void append_basename(struct strbuf *path, const char *dir, const char *file)
{
	const char *tail = strrchr(file, '/');

	strbuf_addstr(path, dir);
	while (path->len && path->buf[path->len - 1] == '/')
		path->len--;
	strbuf_addch(path, '/');
	strbuf_addstr(path, tail ? tail + 1 : file);
}

/*
 * DWIM "diff D F" into "diff D/F F" and "diff F D" into "diff F D/F"
 * Note that we append the basename of F to D/, so "diff a/b/file D"
 * becomes "diff a/b/file D/file", not "diff a/b/file D/a/b/file".
 */
static void fixup_paths(const char **path, struct strbuf *replacement)
{
	unsigned int isdir0, isdir1;

	if (path[0] == file_from_standard_input ||
	    path[1] == file_from_standard_input)
		return;
	isdir0 = is_directory(path[0]);
	isdir1 = is_directory(path[1]);
	if (isdir0 == isdir1)
		return;
	if (isdir0) {
		append_basename(replacement, path[0], path[1]);
		path[0] = replacement->buf;
	} else {
		append_basename(replacement, path[1], path[0]);
		path[1] = replacement->buf;
	}
}

239
void diff_no_index(struct rev_info *revs,
240
		   int argc, const char **argv)
241
{
242
	int i, prefixlen;
243
	const char *paths[2];
244
	struct strbuf replacement = STRBUF_INIT;
245
	const char *prefix = revs->prefix;
246 247 248 249 250 251

	diff_setup(&revs->diffopt);
	for (i = 1; i < argc - 2; ) {
		int j;
		if (!strcmp(argv[i], "--no-index"))
			i++;
252 253
		else if (!strcmp(argv[i], "--"))
			i++;
254
		else {
255 256
			j = diff_opt_parse(&revs->diffopt, argv + i, argc - i,
					   revs->prefix);
257
			if (j <= 0)
258 259 260 261 262
				die("invalid diff option/value: %s", argv[i]);
			i += j;
		}
	}

263 264 265 266
	prefixlen = prefix ? strlen(prefix) : 0;
	for (i = 0; i < 2; i++) {
		const char *p = argv[argc - 2 + i];
		if (!strcmp(p, "-"))
267
			/*
268 269
			 * stdin should be spelled as "-"; if you have
			 * path that is "-", spell it as "./-".
270
			 */
271
			p = file_from_standard_input;
272 273 274
		else if (prefixlen)
			p = xstrdup(prefix_filename(prefix, prefixlen, p));
		paths[i] = p;
275
	}
276 277 278

	fixup_paths(paths, &replacement);

279
	revs->diffopt.skip_stat_unmatch = 1;
280 281
	if (!revs->diffopt.output_format)
		revs->diffopt.output_format = DIFF_FORMAT_PATCH;
282 283 284 285

	DIFF_OPT_SET(&revs->diffopt, NO_INDEX);

	revs->max_count = -2;
T
Thomas Rast 已提交
286
	diff_setup_done(&revs->diffopt);
287

288 289 290
	setup_diff_pager(&revs->diffopt);
	DIFF_OPT_SET(&revs->diffopt, EXIT_WITH_STATUS);

291
	if (queue_diff(&revs->diffopt, paths[0], paths[1]))
292
		exit(1);
293
	diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
294 295 296
	diffcore_std(&revs->diffopt);
	diff_flush(&revs->diffopt);

297 298
	strbuf_release(&replacement);

299 300 301 302
	/*
	 * The return code for --no-index imitates diff(1):
	 * 0 = no changes, 1 = changes, else error
	 */
303
	exit(diff_result_code(&revs->diffopt, 0));
304
}