diff-no-index.c 6.93 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
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)
68
		return error_errno("error while reading from stdin");
69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84

	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);
85
	fill_filespec(s, &null_oid, 0, mode);
86 87 88 89 90
	if (name == file_from_standard_input)
		populate_from_stdin(s);
	return s;
}

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

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

99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119
	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 */
	}
120 121

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

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

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

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

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

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

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

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

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

			ret = queue_diff(o, n1, n2);
		}
178 179
		string_list_clear(&p1, 0);
		string_list_clear(&p2, 0);
180 181
		strbuf_release(&buffer1);
		strbuf_release(&buffer2);
182 183 184 185 186

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

187
		if (o->flags.reverse_diff) {
René Scharfe's avatar
René Scharfe committed
188
			SWAP(mode1, mode2);
René Scharfe's avatar
René Scharfe committed
189
			SWAP(name1, name2);
190 191
		}

192 193
		d1 = noindex_filespec(name1, mode1);
		d2 = noindex_filespec(name2, mode2);
194 195 196 197 198
		diff_queue(&diff_queued_diff, d1, d2);
		return 0;
	}
}

199 200 201 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
/* 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;
	}
}

236 237
void diff_no_index(struct repository *r,
		   struct rev_info *revs,
238
		   int argc, const char **argv)
239
{
240
	int i;
241
	const char *paths[2];
242
	struct strbuf replacement = STRBUF_INIT;
243
	const char *prefix = revs->prefix;
244

245 246 247 248 249
	/*
	 * FIXME: --no-index should not look at index and we should be
	 * able to pass NULL repo. Maybe later.
	 */
	repo_diff_setup(r, &revs->diffopt);
250 251 252 253
	for (i = 1; i < argc - 2; ) {
		int j;
		if (!strcmp(argv[i], "--no-index"))
			i++;
254 255
		else if (!strcmp(argv[i], "--"))
			i++;
256
		else {
257 258
			j = diff_opt_parse(&revs->diffopt, argv + i, argc - i,
					   revs->prefix);
259
			if (j <= 0)
260 261 262 263 264
				die("invalid diff option/value: %s", argv[i]);
			i += j;
		}
	}

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

	fixup_paths(paths, &replacement);

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

284
	revs->diffopt.flags.no_index = 1;
285

286
	revs->diffopt.flags.relative_name = 1;
287 288
	revs->diffopt.prefix = prefix;

289
	revs->max_count = -2;
290
	diff_setup_done(&revs->diffopt);
291

292
	setup_diff_pager(&revs->diffopt);
293
	revs->diffopt.flags.exit_with_status = 1;
294

295
	if (queue_diff(&revs->diffopt, paths[0], paths[1]))
296
		exit(1);
297
	diff_set_mnemonic_prefix(&revs->diffopt, "1/", "2/");
298 299 300
	diffcore_std(&revs->diffopt);
	diff_flush(&revs->diffopt);

301 302
	strbuf_release(&replacement);

303 304 305 306
	/*
	 * The return code for --no-index imitates diff(1):
	 * 0 = no changes, 1 = changes, else error
	 */
307
	exit(diff_result_code(&revs->diffopt, 0));
308
}