##// END OF EJS Templates
merge with stable
Martin von Zweigbergk -
r38445:00368bc0 merge default
parent child Browse files
Show More
@@ -1,940 +1,952 b''
1 /*
1 /*
2 * manifest.c - manifest type that does on-demand parsing.
2 * manifest.c - manifest type that does on-demand parsing.
3 *
3 *
4 * Copyright 2015, Google Inc.
4 * Copyright 2015, Google Inc.
5 *
5 *
6 * This software may be used and distributed according to the terms of
6 * This software may be used and distributed according to the terms of
7 * the GNU General Public License, incorporated herein by reference.
7 * the GNU General Public License, incorporated herein by reference.
8 */
8 */
9 #include <Python.h>
9 #include <Python.h>
10
10
11 #include <assert.h>
11 #include <assert.h>
12 #include <stdlib.h>
12 #include <stdlib.h>
13 #include <string.h>
13 #include <string.h>
14
14
15 #include "charencode.h"
15 #include "charencode.h"
16 #include "util.h"
16 #include "util.h"
17
17
18 #define DEFAULT_LINES 100000
18 #define DEFAULT_LINES 100000
19
19
20 typedef struct {
20 typedef struct {
21 char *start;
21 char *start;
22 Py_ssize_t len; /* length of line including terminal newline */
22 Py_ssize_t len; /* length of line including terminal newline */
23 char hash_suffix;
23 char hash_suffix;
24 bool from_malloc;
24 bool from_malloc;
25 bool deleted;
25 bool deleted;
26 } line;
26 } line;
27
27
28 typedef struct {
28 typedef struct {
29 PyObject_HEAD
29 PyObject_HEAD
30 PyObject *pydata;
30 PyObject *pydata;
31 line *lines;
31 line *lines;
32 int numlines; /* number of line entries */
32 int numlines; /* number of line entries */
33 int livelines; /* number of non-deleted lines */
33 int livelines; /* number of non-deleted lines */
34 int maxlines; /* allocated number of lines */
34 int maxlines; /* allocated number of lines */
35 bool dirty;
35 bool dirty;
36 } lazymanifest;
36 } lazymanifest;
37
37
38 #define MANIFEST_OOM -1
38 #define MANIFEST_OOM -1
39 #define MANIFEST_NOT_SORTED -2
39 #define MANIFEST_NOT_SORTED -2
40 #define MANIFEST_MALFORMED -3
40 #define MANIFEST_MALFORMED -3
41
41
42 /* get the length of the path for a line */
42 /* get the length of the path for a line */
43 static size_t pathlen(line *l)
43 static size_t pathlen(line *l)
44 {
44 {
45 return strlen(l->start);
45 return strlen(l->start);
46 }
46 }
47
47
48 /* get the node value of a single line */
48 /* get the node value of a single line */
49 static PyObject *nodeof(line *l)
49 static PyObject *nodeof(line *l)
50 {
50 {
51 char *s = l->start;
51 char *s = l->start;
52 ssize_t llen = pathlen(l);
52 ssize_t llen = pathlen(l);
53 PyObject *hash = unhexlify(s + llen + 1, 40);
53 PyObject *hash = unhexlify(s + llen + 1, 40);
54 if (!hash) {
54 if (!hash) {
55 return NULL;
55 return NULL;
56 }
56 }
57 if (l->hash_suffix != '\0') {
57 if (l->hash_suffix != '\0') {
58 char newhash[21];
58 char newhash[21];
59 memcpy(newhash, PyBytes_AsString(hash), 20);
59 memcpy(newhash, PyBytes_AsString(hash), 20);
60 Py_DECREF(hash);
60 Py_DECREF(hash);
61 newhash[20] = l->hash_suffix;
61 newhash[20] = l->hash_suffix;
62 hash = PyBytes_FromStringAndSize(newhash, 21);
62 hash = PyBytes_FromStringAndSize(newhash, 21);
63 }
63 }
64 return hash;
64 return hash;
65 }
65 }
66
66
67 /* get the node hash and flags of a line as a tuple */
67 /* get the node hash and flags of a line as a tuple */
68 static PyObject *hashflags(line *l)
68 static PyObject *hashflags(line *l)
69 {
69 {
70 char *s = l->start;
70 char *s = l->start;
71 size_t plen = pathlen(l);
71 size_t plen = pathlen(l);
72 PyObject *hash = nodeof(l);
72 PyObject *hash = nodeof(l);
73
73
74 /* 40 for hash, 1 for null byte, 1 for newline */
74 /* 40 for hash, 1 for null byte, 1 for newline */
75 size_t hplen = plen + 42;
75 size_t hplen = plen + 42;
76 Py_ssize_t flen = l->len - hplen;
76 Py_ssize_t flen = l->len - hplen;
77 PyObject *flags;
77 PyObject *flags;
78 PyObject *tup;
78 PyObject *tup;
79
79
80 if (!hash)
80 if (!hash)
81 return NULL;
81 return NULL;
82 flags = PyBytes_FromStringAndSize(s + hplen - 1, flen);
82 flags = PyBytes_FromStringAndSize(s + hplen - 1, flen);
83 if (!flags) {
83 if (!flags) {
84 Py_DECREF(hash);
84 Py_DECREF(hash);
85 return NULL;
85 return NULL;
86 }
86 }
87 tup = PyTuple_Pack(2, hash, flags);
87 tup = PyTuple_Pack(2, hash, flags);
88 Py_DECREF(flags);
88 Py_DECREF(flags);
89 Py_DECREF(hash);
89 Py_DECREF(hash);
90 return tup;
90 return tup;
91 }
91 }
92
92
93 /* if we're about to run out of space in the line index, add more */
93 /* if we're about to run out of space in the line index, add more */
94 static bool realloc_if_full(lazymanifest *self)
94 static bool realloc_if_full(lazymanifest *self)
95 {
95 {
96 if (self->numlines == self->maxlines) {
96 if (self->numlines == self->maxlines) {
97 self->maxlines *= 2;
97 self->maxlines *= 2;
98 self->lines = realloc(self->lines, self->maxlines * sizeof(line));
98 self->lines = realloc(self->lines, self->maxlines * sizeof(line));
99 }
99 }
100 return !!self->lines;
100 return !!self->lines;
101 }
101 }
102
102
103 /*
103 /*
104 * Find the line boundaries in the manifest that 'data' points to and store
104 * Find the line boundaries in the manifest that 'data' points to and store
105 * information about each line in 'self'.
105 * information about each line in 'self'.
106 */
106 */
107 static int find_lines(lazymanifest *self, char *data, Py_ssize_t len)
107 static int find_lines(lazymanifest *self, char *data, Py_ssize_t len)
108 {
108 {
109 char *prev = NULL;
109 char *prev = NULL;
110 while (len > 0) {
110 while (len > 0) {
111 line *l;
111 line *l;
112 char *next = memchr(data, '\n', len);
112 char *next = memchr(data, '\n', len);
113 if (!next) {
113 if (!next) {
114 return MANIFEST_MALFORMED;
114 return MANIFEST_MALFORMED;
115 }
115 }
116 next++; /* advance past newline */
116 next++; /* advance past newline */
117 if (!realloc_if_full(self)) {
117 if (!realloc_if_full(self)) {
118 return MANIFEST_OOM; /* no memory */
118 return MANIFEST_OOM; /* no memory */
119 }
119 }
120 if (prev && strcmp(prev, data) > -1) {
120 if (prev && strcmp(prev, data) > -1) {
121 /* This data isn't sorted, so we have to abort. */
121 /* This data isn't sorted, so we have to abort. */
122 return MANIFEST_NOT_SORTED;
122 return MANIFEST_NOT_SORTED;
123 }
123 }
124 l = self->lines + ((self->numlines)++);
124 l = self->lines + ((self->numlines)++);
125 l->start = data;
125 l->start = data;
126 l->len = next - data;
126 l->len = next - data;
127 l->hash_suffix = '\0';
127 l->hash_suffix = '\0';
128 l->from_malloc = false;
128 l->from_malloc = false;
129 l->deleted = false;
129 l->deleted = false;
130 len = len - l->len;
130 len = len - l->len;
131 prev = data;
131 prev = data;
132 data = next;
132 data = next;
133 }
133 }
134 self->livelines = self->numlines;
134 self->livelines = self->numlines;
135 return 0;
135 return 0;
136 }
136 }
137
137
138 static void lazymanifest_init_early(lazymanifest *self)
139 {
140 self->pydata = NULL;
141 self->lines = NULL;
142 self->numlines = 0;
143 self->maxlines = 0;
144 }
145
138 static int lazymanifest_init(lazymanifest *self, PyObject *args)
146 static int lazymanifest_init(lazymanifest *self, PyObject *args)
139 {
147 {
140 char *data;
148 char *data;
141 Py_ssize_t len;
149 Py_ssize_t len;
142 int err, ret;
150 int err, ret;
143 PyObject *pydata;
151 PyObject *pydata;
152
153 lazymanifest_init_early(self);
144 if (!PyArg_ParseTuple(args, "S", &pydata)) {
154 if (!PyArg_ParseTuple(args, "S", &pydata)) {
145 return -1;
155 return -1;
146 }
156 }
147 err = PyBytes_AsStringAndSize(pydata, &data, &len);
157 err = PyBytes_AsStringAndSize(pydata, &data, &len);
148
158
149 self->dirty = false;
159 self->dirty = false;
150 if (err == -1)
160 if (err == -1)
151 return -1;
161 return -1;
152 self->pydata = pydata;
162 self->pydata = pydata;
153 Py_INCREF(self->pydata);
163 Py_INCREF(self->pydata);
154 Py_BEGIN_ALLOW_THREADS
164 Py_BEGIN_ALLOW_THREADS
155 self->lines = malloc(DEFAULT_LINES * sizeof(line));
165 self->lines = malloc(DEFAULT_LINES * sizeof(line));
156 self->maxlines = DEFAULT_LINES;
166 self->maxlines = DEFAULT_LINES;
157 self->numlines = 0;
167 self->numlines = 0;
158 if (!self->lines)
168 if (!self->lines)
159 ret = MANIFEST_OOM;
169 ret = MANIFEST_OOM;
160 else
170 else
161 ret = find_lines(self, data, len);
171 ret = find_lines(self, data, len);
162 Py_END_ALLOW_THREADS
172 Py_END_ALLOW_THREADS
163 switch (ret) {
173 switch (ret) {
164 case 0:
174 case 0:
165 break;
175 break;
166 case MANIFEST_OOM:
176 case MANIFEST_OOM:
167 PyErr_NoMemory();
177 PyErr_NoMemory();
168 break;
178 break;
169 case MANIFEST_NOT_SORTED:
179 case MANIFEST_NOT_SORTED:
170 PyErr_Format(PyExc_ValueError,
180 PyErr_Format(PyExc_ValueError,
171 "Manifest lines not in sorted order.");
181 "Manifest lines not in sorted order.");
172 break;
182 break;
173 case MANIFEST_MALFORMED:
183 case MANIFEST_MALFORMED:
174 PyErr_Format(PyExc_ValueError,
184 PyErr_Format(PyExc_ValueError,
175 "Manifest did not end in a newline.");
185 "Manifest did not end in a newline.");
176 break;
186 break;
177 default:
187 default:
178 PyErr_Format(PyExc_ValueError,
188 PyErr_Format(PyExc_ValueError,
179 "Unknown problem parsing manifest.");
189 "Unknown problem parsing manifest.");
180 }
190 }
181 return ret == 0 ? 0 : -1;
191 return ret == 0 ? 0 : -1;
182 }
192 }
183
193
184 static void lazymanifest_dealloc(lazymanifest *self)
194 static void lazymanifest_dealloc(lazymanifest *self)
185 {
195 {
186 /* free any extra lines we had to allocate */
196 /* free any extra lines we had to allocate */
187 int i;
197 int i;
188 for (i = 0; self->lines && (i < self->numlines); i++) {
198 for (i = 0; self->lines && (i < self->numlines); i++) {
189 if (self->lines[i].from_malloc) {
199 if (self->lines[i].from_malloc) {
190 free(self->lines[i].start);
200 free(self->lines[i].start);
191 }
201 }
192 }
202 }
193 free(self->lines);
203 free(self->lines);
194 self->lines = NULL;
204 self->lines = NULL;
195 if (self->pydata) {
205 if (self->pydata) {
196 Py_DECREF(self->pydata);
206 Py_DECREF(self->pydata);
197 self->pydata = NULL;
207 self->pydata = NULL;
198 }
208 }
199 PyObject_Del(self);
209 PyObject_Del(self);
200 }
210 }
201
211
202 /* iteration support */
212 /* iteration support */
203
213
204 typedef struct {
214 typedef struct {
205 PyObject_HEAD lazymanifest *m;
215 PyObject_HEAD lazymanifest *m;
206 Py_ssize_t pos;
216 Py_ssize_t pos;
207 } lmIter;
217 } lmIter;
208
218
209 static void lmiter_dealloc(PyObject *o)
219 static void lmiter_dealloc(PyObject *o)
210 {
220 {
211 lmIter *self = (lmIter *)o;
221 lmIter *self = (lmIter *)o;
212 Py_DECREF(self->m);
222 Py_DECREF(self->m);
213 PyObject_Del(self);
223 PyObject_Del(self);
214 }
224 }
215
225
216 static line *lmiter_nextline(lmIter *self)
226 static line *lmiter_nextline(lmIter *self)
217 {
227 {
218 do {
228 do {
219 self->pos++;
229 self->pos++;
220 if (self->pos >= self->m->numlines) {
230 if (self->pos >= self->m->numlines) {
221 return NULL;
231 return NULL;
222 }
232 }
223 /* skip over deleted manifest entries */
233 /* skip over deleted manifest entries */
224 } while (self->m->lines[self->pos].deleted);
234 } while (self->m->lines[self->pos].deleted);
225 return self->m->lines + self->pos;
235 return self->m->lines + self->pos;
226 }
236 }
227
237
228 static PyObject *lmiter_iterentriesnext(PyObject *o)
238 static PyObject *lmiter_iterentriesnext(PyObject *o)
229 {
239 {
230 size_t pl;
240 size_t pl;
231 line *l;
241 line *l;
232 Py_ssize_t consumed;
242 Py_ssize_t consumed;
233 PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL;
243 PyObject *ret = NULL, *path = NULL, *hash = NULL, *flags = NULL;
234 l = lmiter_nextline((lmIter *)o);
244 l = lmiter_nextline((lmIter *)o);
235 if (!l) {
245 if (!l) {
236 goto done;
246 goto done;
237 }
247 }
238 pl = pathlen(l);
248 pl = pathlen(l);
239 path = PyBytes_FromStringAndSize(l->start, pl);
249 path = PyBytes_FromStringAndSize(l->start, pl);
240 hash = nodeof(l);
250 hash = nodeof(l);
241 consumed = pl + 41;
251 consumed = pl + 41;
242 flags = PyBytes_FromStringAndSize(l->start + consumed,
252 flags = PyBytes_FromStringAndSize(l->start + consumed,
243 l->len - consumed - 1);
253 l->len - consumed - 1);
244 if (!path || !hash || !flags) {
254 if (!path || !hash || !flags) {
245 goto done;
255 goto done;
246 }
256 }
247 ret = PyTuple_Pack(3, path, hash, flags);
257 ret = PyTuple_Pack(3, path, hash, flags);
248 done:
258 done:
249 Py_XDECREF(path);
259 Py_XDECREF(path);
250 Py_XDECREF(hash);
260 Py_XDECREF(hash);
251 Py_XDECREF(flags);
261 Py_XDECREF(flags);
252 return ret;
262 return ret;
253 }
263 }
254
264
255 #ifdef IS_PY3K
265 #ifdef IS_PY3K
256 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
266 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
257 #else
267 #else
258 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
268 #define LAZYMANIFESTENTRIESITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
259 | Py_TPFLAGS_HAVE_ITER
269 | Py_TPFLAGS_HAVE_ITER
260 #endif
270 #endif
261
271
262 static PyTypeObject lazymanifestEntriesIterator = {
272 static PyTypeObject lazymanifestEntriesIterator = {
263 PyVarObject_HEAD_INIT(NULL, 0) /* header */
273 PyVarObject_HEAD_INIT(NULL, 0) /* header */
264 "parsers.lazymanifest.entriesiterator", /*tp_name */
274 "parsers.lazymanifest.entriesiterator", /*tp_name */
265 sizeof(lmIter), /*tp_basicsize */
275 sizeof(lmIter), /*tp_basicsize */
266 0, /*tp_itemsize */
276 0, /*tp_itemsize */
267 lmiter_dealloc, /*tp_dealloc */
277 lmiter_dealloc, /*tp_dealloc */
268 0, /*tp_print */
278 0, /*tp_print */
269 0, /*tp_getattr */
279 0, /*tp_getattr */
270 0, /*tp_setattr */
280 0, /*tp_setattr */
271 0, /*tp_compare */
281 0, /*tp_compare */
272 0, /*tp_repr */
282 0, /*tp_repr */
273 0, /*tp_as_number */
283 0, /*tp_as_number */
274 0, /*tp_as_sequence */
284 0, /*tp_as_sequence */
275 0, /*tp_as_mapping */
285 0, /*tp_as_mapping */
276 0, /*tp_hash */
286 0, /*tp_hash */
277 0, /*tp_call */
287 0, /*tp_call */
278 0, /*tp_str */
288 0, /*tp_str */
279 0, /*tp_getattro */
289 0, /*tp_getattro */
280 0, /*tp_setattro */
290 0, /*tp_setattro */
281 0, /*tp_as_buffer */
291 0, /*tp_as_buffer */
282 LAZYMANIFESTENTRIESITERATOR_TPFLAGS, /* tp_flags */
292 LAZYMANIFESTENTRIESITERATOR_TPFLAGS, /* tp_flags */
283 "Iterator for 3-tuples in a lazymanifest.", /* tp_doc */
293 "Iterator for 3-tuples in a lazymanifest.", /* tp_doc */
284 0, /* tp_traverse */
294 0, /* tp_traverse */
285 0, /* tp_clear */
295 0, /* tp_clear */
286 0, /* tp_richcompare */
296 0, /* tp_richcompare */
287 0, /* tp_weaklistoffset */
297 0, /* tp_weaklistoffset */
288 PyObject_SelfIter, /* tp_iter: __iter__() method */
298 PyObject_SelfIter, /* tp_iter: __iter__() method */
289 lmiter_iterentriesnext, /* tp_iternext: next() method */
299 lmiter_iterentriesnext, /* tp_iternext: next() method */
290 };
300 };
291
301
292 static PyObject *lmiter_iterkeysnext(PyObject *o)
302 static PyObject *lmiter_iterkeysnext(PyObject *o)
293 {
303 {
294 size_t pl;
304 size_t pl;
295 line *l = lmiter_nextline((lmIter *)o);
305 line *l = lmiter_nextline((lmIter *)o);
296 if (!l) {
306 if (!l) {
297 return NULL;
307 return NULL;
298 }
308 }
299 pl = pathlen(l);
309 pl = pathlen(l);
300 return PyBytes_FromStringAndSize(l->start, pl);
310 return PyBytes_FromStringAndSize(l->start, pl);
301 }
311 }
302
312
303 #ifdef IS_PY3K
313 #ifdef IS_PY3K
304 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
314 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT
305 #else
315 #else
306 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
316 #define LAZYMANIFESTKEYSITERATOR_TPFLAGS Py_TPFLAGS_DEFAULT \
307 | Py_TPFLAGS_HAVE_ITER
317 | Py_TPFLAGS_HAVE_ITER
308 #endif
318 #endif
309
319
310 static PyTypeObject lazymanifestKeysIterator = {
320 static PyTypeObject lazymanifestKeysIterator = {
311 PyVarObject_HEAD_INIT(NULL, 0) /* header */
321 PyVarObject_HEAD_INIT(NULL, 0) /* header */
312 "parsers.lazymanifest.keysiterator", /*tp_name */
322 "parsers.lazymanifest.keysiterator", /*tp_name */
313 sizeof(lmIter), /*tp_basicsize */
323 sizeof(lmIter), /*tp_basicsize */
314 0, /*tp_itemsize */
324 0, /*tp_itemsize */
315 lmiter_dealloc, /*tp_dealloc */
325 lmiter_dealloc, /*tp_dealloc */
316 0, /*tp_print */
326 0, /*tp_print */
317 0, /*tp_getattr */
327 0, /*tp_getattr */
318 0, /*tp_setattr */
328 0, /*tp_setattr */
319 0, /*tp_compare */
329 0, /*tp_compare */
320 0, /*tp_repr */
330 0, /*tp_repr */
321 0, /*tp_as_number */
331 0, /*tp_as_number */
322 0, /*tp_as_sequence */
332 0, /*tp_as_sequence */
323 0, /*tp_as_mapping */
333 0, /*tp_as_mapping */
324 0, /*tp_hash */
334 0, /*tp_hash */
325 0, /*tp_call */
335 0, /*tp_call */
326 0, /*tp_str */
336 0, /*tp_str */
327 0, /*tp_getattro */
337 0, /*tp_getattro */
328 0, /*tp_setattro */
338 0, /*tp_setattro */
329 0, /*tp_as_buffer */
339 0, /*tp_as_buffer */
330 LAZYMANIFESTKEYSITERATOR_TPFLAGS, /* tp_flags */
340 LAZYMANIFESTKEYSITERATOR_TPFLAGS, /* tp_flags */
331 "Keys iterator for a lazymanifest.", /* tp_doc */
341 "Keys iterator for a lazymanifest.", /* tp_doc */
332 0, /* tp_traverse */
342 0, /* tp_traverse */
333 0, /* tp_clear */
343 0, /* tp_clear */
334 0, /* tp_richcompare */
344 0, /* tp_richcompare */
335 0, /* tp_weaklistoffset */
345 0, /* tp_weaklistoffset */
336 PyObject_SelfIter, /* tp_iter: __iter__() method */
346 PyObject_SelfIter, /* tp_iter: __iter__() method */
337 lmiter_iterkeysnext, /* tp_iternext: next() method */
347 lmiter_iterkeysnext, /* tp_iternext: next() method */
338 };
348 };
339
349
340 static lazymanifest *lazymanifest_copy(lazymanifest *self);
350 static lazymanifest *lazymanifest_copy(lazymanifest *self);
341
351
342 static PyObject *lazymanifest_getentriesiter(lazymanifest *self)
352 static PyObject *lazymanifest_getentriesiter(lazymanifest *self)
343 {
353 {
344 lmIter *i = NULL;
354 lmIter *i = NULL;
345 lazymanifest *t = lazymanifest_copy(self);
355 lazymanifest *t = lazymanifest_copy(self);
346 if (!t) {
356 if (!t) {
347 PyErr_NoMemory();
357 PyErr_NoMemory();
348 return NULL;
358 return NULL;
349 }
359 }
350 i = PyObject_New(lmIter, &lazymanifestEntriesIterator);
360 i = PyObject_New(lmIter, &lazymanifestEntriesIterator);
351 if (i) {
361 if (i) {
352 i->m = t;
362 i->m = t;
353 i->pos = -1;
363 i->pos = -1;
354 } else {
364 } else {
355 Py_DECREF(t);
365 Py_DECREF(t);
356 PyErr_NoMemory();
366 PyErr_NoMemory();
357 }
367 }
358 return (PyObject *)i;
368 return (PyObject *)i;
359 }
369 }
360
370
361 static PyObject *lazymanifest_getkeysiter(lazymanifest *self)
371 static PyObject *lazymanifest_getkeysiter(lazymanifest *self)
362 {
372 {
363 lmIter *i = NULL;
373 lmIter *i = NULL;
364 lazymanifest *t = lazymanifest_copy(self);
374 lazymanifest *t = lazymanifest_copy(self);
365 if (!t) {
375 if (!t) {
366 PyErr_NoMemory();
376 PyErr_NoMemory();
367 return NULL;
377 return NULL;
368 }
378 }
369 i = PyObject_New(lmIter, &lazymanifestKeysIterator);
379 i = PyObject_New(lmIter, &lazymanifestKeysIterator);
370 if (i) {
380 if (i) {
371 i->m = t;
381 i->m = t;
372 i->pos = -1;
382 i->pos = -1;
373 } else {
383 } else {
374 Py_DECREF(t);
384 Py_DECREF(t);
375 PyErr_NoMemory();
385 PyErr_NoMemory();
376 }
386 }
377 return (PyObject *)i;
387 return (PyObject *)i;
378 }
388 }
379
389
380 /* __getitem__ and __setitem__ support */
390 /* __getitem__ and __setitem__ support */
381
391
382 static Py_ssize_t lazymanifest_size(lazymanifest *self)
392 static Py_ssize_t lazymanifest_size(lazymanifest *self)
383 {
393 {
384 return self->livelines;
394 return self->livelines;
385 }
395 }
386
396
387 static int linecmp(const void *left, const void *right)
397 static int linecmp(const void *left, const void *right)
388 {
398 {
389 return strcmp(((const line *)left)->start,
399 return strcmp(((const line *)left)->start,
390 ((const line *)right)->start);
400 ((const line *)right)->start);
391 }
401 }
392
402
393 static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key)
403 static PyObject *lazymanifest_getitem(lazymanifest *self, PyObject *key)
394 {
404 {
395 line needle;
405 line needle;
396 line *hit;
406 line *hit;
397 if (!PyBytes_Check(key)) {
407 if (!PyBytes_Check(key)) {
398 PyErr_Format(PyExc_TypeError,
408 PyErr_Format(PyExc_TypeError,
399 "getitem: manifest keys must be a string.");
409 "getitem: manifest keys must be a string.");
400 return NULL;
410 return NULL;
401 }
411 }
402 needle.start = PyBytes_AsString(key);
412 needle.start = PyBytes_AsString(key);
403 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
413 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
404 &linecmp);
414 &linecmp);
405 if (!hit || hit->deleted) {
415 if (!hit || hit->deleted) {
406 PyErr_Format(PyExc_KeyError, "No such manifest entry.");
416 PyErr_Format(PyExc_KeyError, "No such manifest entry.");
407 return NULL;
417 return NULL;
408 }
418 }
409 return hashflags(hit);
419 return hashflags(hit);
410 }
420 }
411
421
412 static int lazymanifest_delitem(lazymanifest *self, PyObject *key)
422 static int lazymanifest_delitem(lazymanifest *self, PyObject *key)
413 {
423 {
414 line needle;
424 line needle;
415 line *hit;
425 line *hit;
416 if (!PyBytes_Check(key)) {
426 if (!PyBytes_Check(key)) {
417 PyErr_Format(PyExc_TypeError,
427 PyErr_Format(PyExc_TypeError,
418 "delitem: manifest keys must be a string.");
428 "delitem: manifest keys must be a string.");
419 return -1;
429 return -1;
420 }
430 }
421 needle.start = PyBytes_AsString(key);
431 needle.start = PyBytes_AsString(key);
422 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
432 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
423 &linecmp);
433 &linecmp);
424 if (!hit || hit->deleted) {
434 if (!hit || hit->deleted) {
425 PyErr_Format(PyExc_KeyError,
435 PyErr_Format(PyExc_KeyError,
426 "Tried to delete nonexistent manifest entry.");
436 "Tried to delete nonexistent manifest entry.");
427 return -1;
437 return -1;
428 }
438 }
429 self->dirty = true;
439 self->dirty = true;
430 hit->deleted = true;
440 hit->deleted = true;
431 self->livelines--;
441 self->livelines--;
432 return 0;
442 return 0;
433 }
443 }
434
444
435 /* Do a binary search for the insertion point for new, creating the
445 /* Do a binary search for the insertion point for new, creating the
436 * new entry if needed. */
446 * new entry if needed. */
437 static int internalsetitem(lazymanifest *self, line *new)
447 static int internalsetitem(lazymanifest *self, line *new)
438 {
448 {
439 int start = 0, end = self->numlines;
449 int start = 0, end = self->numlines;
440 while (start < end) {
450 while (start < end) {
441 int pos = start + (end - start) / 2;
451 int pos = start + (end - start) / 2;
442 int c = linecmp(new, self->lines + pos);
452 int c = linecmp(new, self->lines + pos);
443 if (c < 0)
453 if (c < 0)
444 end = pos;
454 end = pos;
445 else if (c > 0)
455 else if (c > 0)
446 start = pos + 1;
456 start = pos + 1;
447 else {
457 else {
448 if (self->lines[pos].deleted)
458 if (self->lines[pos].deleted)
449 self->livelines++;
459 self->livelines++;
450 if (self->lines[pos].from_malloc)
460 if (self->lines[pos].from_malloc)
451 free(self->lines[pos].start);
461 free(self->lines[pos].start);
452 start = pos;
462 start = pos;
453 goto finish;
463 goto finish;
454 }
464 }
455 }
465 }
456 /* being here means we need to do an insert */
466 /* being here means we need to do an insert */
457 if (!realloc_if_full(self)) {
467 if (!realloc_if_full(self)) {
458 PyErr_NoMemory();
468 PyErr_NoMemory();
459 return -1;
469 return -1;
460 }
470 }
461 memmove(self->lines + start + 1, self->lines + start,
471 memmove(self->lines + start + 1, self->lines + start,
462 (self->numlines - start) * sizeof(line));
472 (self->numlines - start) * sizeof(line));
463 self->numlines++;
473 self->numlines++;
464 self->livelines++;
474 self->livelines++;
465 finish:
475 finish:
466 self->lines[start] = *new;
476 self->lines[start] = *new;
467 self->dirty = true;
477 self->dirty = true;
468 return 0;
478 return 0;
469 }
479 }
470
480
471 static int lazymanifest_setitem(
481 static int lazymanifest_setitem(
472 lazymanifest *self, PyObject *key, PyObject *value)
482 lazymanifest *self, PyObject *key, PyObject *value)
473 {
483 {
474 char *path;
484 char *path;
475 Py_ssize_t plen;
485 Py_ssize_t plen;
476 PyObject *pyhash;
486 PyObject *pyhash;
477 Py_ssize_t hlen;
487 Py_ssize_t hlen;
478 char *hash;
488 char *hash;
479 PyObject *pyflags;
489 PyObject *pyflags;
480 char *flags;
490 char *flags;
481 Py_ssize_t flen;
491 Py_ssize_t flen;
482 size_t dlen;
492 size_t dlen;
483 char *dest;
493 char *dest;
484 int i;
494 int i;
485 line new;
495 line new;
486 if (!PyBytes_Check(key)) {
496 if (!PyBytes_Check(key)) {
487 PyErr_Format(PyExc_TypeError,
497 PyErr_Format(PyExc_TypeError,
488 "setitem: manifest keys must be a string.");
498 "setitem: manifest keys must be a string.");
489 return -1;
499 return -1;
490 }
500 }
491 if (!value) {
501 if (!value) {
492 return lazymanifest_delitem(self, key);
502 return lazymanifest_delitem(self, key);
493 }
503 }
494 if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) {
504 if (!PyTuple_Check(value) || PyTuple_Size(value) != 2) {
495 PyErr_Format(PyExc_TypeError,
505 PyErr_Format(PyExc_TypeError,
496 "Manifest values must be a tuple of (node, flags).");
506 "Manifest values must be a tuple of (node, flags).");
497 return -1;
507 return -1;
498 }
508 }
499 if (PyBytes_AsStringAndSize(key, &path, &plen) == -1) {
509 if (PyBytes_AsStringAndSize(key, &path, &plen) == -1) {
500 return -1;
510 return -1;
501 }
511 }
502
512
503 pyhash = PyTuple_GetItem(value, 0);
513 pyhash = PyTuple_GetItem(value, 0);
504 if (!PyBytes_Check(pyhash)) {
514 if (!PyBytes_Check(pyhash)) {
505 PyErr_Format(PyExc_TypeError,
515 PyErr_Format(PyExc_TypeError,
506 "node must be a 20-byte string");
516 "node must be a 20-byte string");
507 return -1;
517 return -1;
508 }
518 }
509 hlen = PyBytes_Size(pyhash);
519 hlen = PyBytes_Size(pyhash);
510 /* Some parts of the codebase try and set 21 or 22
520 /* Some parts of the codebase try and set 21 or 22
511 * byte "hash" values in order to perturb things for
521 * byte "hash" values in order to perturb things for
512 * status. We have to preserve at least the 21st
522 * status. We have to preserve at least the 21st
513 * byte. Sigh. If there's a 22nd byte, we drop it on
523 * byte. Sigh. If there's a 22nd byte, we drop it on
514 * the floor, which works fine.
524 * the floor, which works fine.
515 */
525 */
516 if (hlen != 20 && hlen != 21 && hlen != 22) {
526 if (hlen != 20 && hlen != 21 && hlen != 22) {
517 PyErr_Format(PyExc_TypeError,
527 PyErr_Format(PyExc_TypeError,
518 "node must be a 20-byte string");
528 "node must be a 20-byte string");
519 return -1;
529 return -1;
520 }
530 }
521 hash = PyBytes_AsString(pyhash);
531 hash = PyBytes_AsString(pyhash);
522
532
523 pyflags = PyTuple_GetItem(value, 1);
533 pyflags = PyTuple_GetItem(value, 1);
524 if (!PyBytes_Check(pyflags) || PyBytes_Size(pyflags) > 1) {
534 if (!PyBytes_Check(pyflags) || PyBytes_Size(pyflags) > 1) {
525 PyErr_Format(PyExc_TypeError,
535 PyErr_Format(PyExc_TypeError,
526 "flags must a 0 or 1 byte string");
536 "flags must a 0 or 1 byte string");
527 return -1;
537 return -1;
528 }
538 }
529 if (PyBytes_AsStringAndSize(pyflags, &flags, &flen) == -1) {
539 if (PyBytes_AsStringAndSize(pyflags, &flags, &flen) == -1) {
530 return -1;
540 return -1;
531 }
541 }
532 /* one null byte and one newline */
542 /* one null byte and one newline */
533 dlen = plen + 41 + flen + 1;
543 dlen = plen + 41 + flen + 1;
534 dest = malloc(dlen);
544 dest = malloc(dlen);
535 if (!dest) {
545 if (!dest) {
536 PyErr_NoMemory();
546 PyErr_NoMemory();
537 return -1;
547 return -1;
538 }
548 }
539 memcpy(dest, path, plen + 1);
549 memcpy(dest, path, plen + 1);
540 for (i = 0; i < 20; i++) {
550 for (i = 0; i < 20; i++) {
541 /* Cast to unsigned, so it will not get sign-extended when promoted
551 /* Cast to unsigned, so it will not get sign-extended when promoted
542 * to int (as is done when passing to a variadic function)
552 * to int (as is done when passing to a variadic function)
543 */
553 */
544 sprintf(dest + plen + 1 + (i * 2), "%02x", (unsigned char)hash[i]);
554 sprintf(dest + plen + 1 + (i * 2), "%02x", (unsigned char)hash[i]);
545 }
555 }
546 memcpy(dest + plen + 41, flags, flen);
556 memcpy(dest + plen + 41, flags, flen);
547 dest[plen + 41 + flen] = '\n';
557 dest[plen + 41 + flen] = '\n';
548 new.start = dest;
558 new.start = dest;
549 new.len = dlen;
559 new.len = dlen;
550 new.hash_suffix = '\0';
560 new.hash_suffix = '\0';
551 if (hlen > 20) {
561 if (hlen > 20) {
552 new.hash_suffix = hash[20];
562 new.hash_suffix = hash[20];
553 }
563 }
554 new.from_malloc = true; /* is `start` a pointer we allocated? */
564 new.from_malloc = true; /* is `start` a pointer we allocated? */
555 new.deleted = false; /* is this entry deleted? */
565 new.deleted = false; /* is this entry deleted? */
556 if (internalsetitem(self, &new)) {
566 if (internalsetitem(self, &new)) {
557 return -1;
567 return -1;
558 }
568 }
559 return 0;
569 return 0;
560 }
570 }
561
571
562 static PyMappingMethods lazymanifest_mapping_methods = {
572 static PyMappingMethods lazymanifest_mapping_methods = {
563 (lenfunc)lazymanifest_size, /* mp_length */
573 (lenfunc)lazymanifest_size, /* mp_length */
564 (binaryfunc)lazymanifest_getitem, /* mp_subscript */
574 (binaryfunc)lazymanifest_getitem, /* mp_subscript */
565 (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */
575 (objobjargproc)lazymanifest_setitem, /* mp_ass_subscript */
566 };
576 };
567
577
568 /* sequence methods (important or __contains__ builds an iterator) */
578 /* sequence methods (important or __contains__ builds an iterator) */
569
579
570 static int lazymanifest_contains(lazymanifest *self, PyObject *key)
580 static int lazymanifest_contains(lazymanifest *self, PyObject *key)
571 {
581 {
572 line needle;
582 line needle;
573 line *hit;
583 line *hit;
574 if (!PyBytes_Check(key)) {
584 if (!PyBytes_Check(key)) {
575 /* Our keys are always strings, so if the contains
585 /* Our keys are always strings, so if the contains
576 * check is for a non-string, just return false. */
586 * check is for a non-string, just return false. */
577 return 0;
587 return 0;
578 }
588 }
579 needle.start = PyBytes_AsString(key);
589 needle.start = PyBytes_AsString(key);
580 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
590 hit = bsearch(&needle, self->lines, self->numlines, sizeof(line),
581 &linecmp);
591 &linecmp);
582 if (!hit || hit->deleted) {
592 if (!hit || hit->deleted) {
583 return 0;
593 return 0;
584 }
594 }
585 return 1;
595 return 1;
586 }
596 }
587
597
588 static PySequenceMethods lazymanifest_seq_meths = {
598 static PySequenceMethods lazymanifest_seq_meths = {
589 (lenfunc)lazymanifest_size, /* sq_length */
599 (lenfunc)lazymanifest_size, /* sq_length */
590 0, /* sq_concat */
600 0, /* sq_concat */
591 0, /* sq_repeat */
601 0, /* sq_repeat */
592 0, /* sq_item */
602 0, /* sq_item */
593 0, /* sq_slice */
603 0, /* sq_slice */
594 0, /* sq_ass_item */
604 0, /* sq_ass_item */
595 0, /* sq_ass_slice */
605 0, /* sq_ass_slice */
596 (objobjproc)lazymanifest_contains, /* sq_contains */
606 (objobjproc)lazymanifest_contains, /* sq_contains */
597 0, /* sq_inplace_concat */
607 0, /* sq_inplace_concat */
598 0, /* sq_inplace_repeat */
608 0, /* sq_inplace_repeat */
599 };
609 };
600
610
601
611
602 /* Other methods (copy, diff, etc) */
612 /* Other methods (copy, diff, etc) */
603 static PyTypeObject lazymanifestType;
613 static PyTypeObject lazymanifestType;
604
614
605 /* If the manifest has changes, build the new manifest text and reindex it. */
615 /* If the manifest has changes, build the new manifest text and reindex it. */
606 static int compact(lazymanifest *self)
616 static int compact(lazymanifest *self)
607 {
617 {
608 int i;
618 int i;
609 ssize_t need = 0;
619 ssize_t need = 0;
610 char *data;
620 char *data;
611 line *src, *dst;
621 line *src, *dst;
612 PyObject *pydata;
622 PyObject *pydata;
613 if (!self->dirty)
623 if (!self->dirty)
614 return 0;
624 return 0;
615 for (i = 0; i < self->numlines; i++) {
625 for (i = 0; i < self->numlines; i++) {
616 if (!self->lines[i].deleted) {
626 if (!self->lines[i].deleted) {
617 need += self->lines[i].len;
627 need += self->lines[i].len;
618 }
628 }
619 }
629 }
620 pydata = PyBytes_FromStringAndSize(NULL, need);
630 pydata = PyBytes_FromStringAndSize(NULL, need);
621 if (!pydata)
631 if (!pydata)
622 return -1;
632 return -1;
623 data = PyBytes_AsString(pydata);
633 data = PyBytes_AsString(pydata);
624 if (!data) {
634 if (!data) {
625 return -1;
635 return -1;
626 }
636 }
627 src = self->lines;
637 src = self->lines;
628 dst = self->lines;
638 dst = self->lines;
629 for (i = 0; i < self->numlines; i++, src++) {
639 for (i = 0; i < self->numlines; i++, src++) {
630 char *tofree = NULL;
640 char *tofree = NULL;
631 if (src->from_malloc) {
641 if (src->from_malloc) {
632 tofree = src->start;
642 tofree = src->start;
633 }
643 }
634 if (!src->deleted) {
644 if (!src->deleted) {
635 memcpy(data, src->start, src->len);
645 memcpy(data, src->start, src->len);
636 *dst = *src;
646 *dst = *src;
637 dst->start = data;
647 dst->start = data;
638 dst->from_malloc = false;
648 dst->from_malloc = false;
639 data += dst->len;
649 data += dst->len;
640 dst++;
650 dst++;
641 }
651 }
642 free(tofree);
652 free(tofree);
643 }
653 }
644 Py_DECREF(self->pydata);
654 Py_DECREF(self->pydata);
645 self->pydata = pydata;
655 self->pydata = pydata;
646 self->numlines = self->livelines;
656 self->numlines = self->livelines;
647 self->dirty = false;
657 self->dirty = false;
648 return 0;
658 return 0;
649 }
659 }
650
660
651 static PyObject *lazymanifest_text(lazymanifest *self)
661 static PyObject *lazymanifest_text(lazymanifest *self)
652 {
662 {
653 if (compact(self) != 0) {
663 if (compact(self) != 0) {
654 PyErr_NoMemory();
664 PyErr_NoMemory();
655 return NULL;
665 return NULL;
656 }
666 }
657 Py_INCREF(self->pydata);
667 Py_INCREF(self->pydata);
658 return self->pydata;
668 return self->pydata;
659 }
669 }
660
670
661 static lazymanifest *lazymanifest_copy(lazymanifest *self)
671 static lazymanifest *lazymanifest_copy(lazymanifest *self)
662 {
672 {
663 lazymanifest *copy = NULL;
673 lazymanifest *copy = NULL;
664 if (compact(self) != 0) {
674 if (compact(self) != 0) {
665 goto nomem;
675 goto nomem;
666 }
676 }
667 copy = PyObject_New(lazymanifest, &lazymanifestType);
677 copy = PyObject_New(lazymanifest, &lazymanifestType);
668 if (!copy) {
678 if (!copy) {
669 goto nomem;
679 goto nomem;
670 }
680 }
681 lazymanifest_init_early(copy);
671 copy->numlines = self->numlines;
682 copy->numlines = self->numlines;
672 copy->livelines = self->livelines;
683 copy->livelines = self->livelines;
673 copy->dirty = false;
684 copy->dirty = false;
674 copy->lines = malloc(self->maxlines *sizeof(line));
685 copy->lines = malloc(self->maxlines *sizeof(line));
675 if (!copy->lines) {
686 if (!copy->lines) {
676 goto nomem;
687 goto nomem;
677 }
688 }
678 memcpy(copy->lines, self->lines, self->numlines * sizeof(line));
689 memcpy(copy->lines, self->lines, self->numlines * sizeof(line));
679 copy->maxlines = self->maxlines;
690 copy->maxlines = self->maxlines;
680 copy->pydata = self->pydata;
691 copy->pydata = self->pydata;
681 Py_INCREF(copy->pydata);
692 Py_INCREF(copy->pydata);
682 return copy;
693 return copy;
683 nomem:
694 nomem:
684 PyErr_NoMemory();
695 PyErr_NoMemory();
685 Py_XDECREF(copy);
696 Py_XDECREF(copy);
686 return NULL;
697 return NULL;
687 }
698 }
688
699
689 static lazymanifest *lazymanifest_filtercopy(
700 static lazymanifest *lazymanifest_filtercopy(
690 lazymanifest *self, PyObject *matchfn)
701 lazymanifest *self, PyObject *matchfn)
691 {
702 {
692 lazymanifest *copy = NULL;
703 lazymanifest *copy = NULL;
693 int i;
704 int i;
694 if (!PyCallable_Check(matchfn)) {
705 if (!PyCallable_Check(matchfn)) {
695 PyErr_SetString(PyExc_TypeError, "matchfn must be callable");
706 PyErr_SetString(PyExc_TypeError, "matchfn must be callable");
696 return NULL;
707 return NULL;
697 }
708 }
698 /* compact ourselves first to avoid double-frees later when we
709 /* compact ourselves first to avoid double-frees later when we
699 * compact tmp so that it doesn't have random pointers to our
710 * compact tmp so that it doesn't have random pointers to our
700 * underlying from_malloc-data (self->pydata is safe) */
711 * underlying from_malloc-data (self->pydata is safe) */
701 if (compact(self) != 0) {
712 if (compact(self) != 0) {
702 goto nomem;
713 goto nomem;
703 }
714 }
704 copy = PyObject_New(lazymanifest, &lazymanifestType);
715 copy = PyObject_New(lazymanifest, &lazymanifestType);
705 if (!copy) {
716 if (!copy) {
706 goto nomem;
717 goto nomem;
707 }
718 }
719 lazymanifest_init_early(copy);
708 copy->dirty = true;
720 copy->dirty = true;
709 copy->lines = malloc(self->maxlines * sizeof(line));
721 copy->lines = malloc(self->maxlines * sizeof(line));
710 if (!copy->lines) {
722 if (!copy->lines) {
711 goto nomem;
723 goto nomem;
712 }
724 }
713 copy->maxlines = self->maxlines;
725 copy->maxlines = self->maxlines;
714 copy->numlines = 0;
726 copy->numlines = 0;
715 copy->pydata = self->pydata;
727 copy->pydata = self->pydata;
716 Py_INCREF(self->pydata);
728 Py_INCREF(self->pydata);
717 for (i = 0; i < self->numlines; i++) {
729 for (i = 0; i < self->numlines; i++) {
718 PyObject *arglist = NULL, *result = NULL;
730 PyObject *arglist = NULL, *result = NULL;
719 arglist = Py_BuildValue(PY23("(s)", "(y)"),
731 arglist = Py_BuildValue(PY23("(s)", "(y)"),
720 self->lines[i].start);
732 self->lines[i].start);
721 if (!arglist) {
733 if (!arglist) {
722 return NULL;
734 return NULL;
723 }
735 }
724 result = PyObject_CallObject(matchfn, arglist);
736 result = PyObject_CallObject(matchfn, arglist);
725 Py_DECREF(arglist);
737 Py_DECREF(arglist);
726 /* if the callback raised an exception, just let it
738 /* if the callback raised an exception, just let it
727 * through and give up */
739 * through and give up */
728 if (!result) {
740 if (!result) {
729 free(copy->lines);
741 free(copy->lines);
730 Py_DECREF(self->pydata);
742 Py_DECREF(self->pydata);
731 return NULL;
743 return NULL;
732 }
744 }
733 if (PyObject_IsTrue(result)) {
745 if (PyObject_IsTrue(result)) {
734 assert(!(self->lines[i].from_malloc));
746 assert(!(self->lines[i].from_malloc));
735 copy->lines[copy->numlines++] = self->lines[i];
747 copy->lines[copy->numlines++] = self->lines[i];
736 }
748 }
737 Py_DECREF(result);
749 Py_DECREF(result);
738 }
750 }
739 copy->livelines = copy->numlines;
751 copy->livelines = copy->numlines;
740 return copy;
752 return copy;
741 nomem:
753 nomem:
742 PyErr_NoMemory();
754 PyErr_NoMemory();
743 Py_XDECREF(copy);
755 Py_XDECREF(copy);
744 return NULL;
756 return NULL;
745 }
757 }
746
758
747 static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args)
759 static PyObject *lazymanifest_diff(lazymanifest *self, PyObject *args)
748 {
760 {
749 lazymanifest *other;
761 lazymanifest *other;
750 PyObject *pyclean = NULL;
762 PyObject *pyclean = NULL;
751 bool listclean;
763 bool listclean;
752 PyObject *emptyTup = NULL, *ret = NULL;
764 PyObject *emptyTup = NULL, *ret = NULL;
753 PyObject *es;
765 PyObject *es;
754 int sneedle = 0, oneedle = 0;
766 int sneedle = 0, oneedle = 0;
755 if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) {
767 if (!PyArg_ParseTuple(args, "O!|O", &lazymanifestType, &other, &pyclean)) {
756 return NULL;
768 return NULL;
757 }
769 }
758 listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean);
770 listclean = (!pyclean) ? false : PyObject_IsTrue(pyclean);
759 es = PyBytes_FromString("");
771 es = PyBytes_FromString("");
760 if (!es) {
772 if (!es) {
761 goto nomem;
773 goto nomem;
762 }
774 }
763 emptyTup = PyTuple_Pack(2, Py_None, es);
775 emptyTup = PyTuple_Pack(2, Py_None, es);
764 Py_DECREF(es);
776 Py_DECREF(es);
765 if (!emptyTup) {
777 if (!emptyTup) {
766 goto nomem;
778 goto nomem;
767 }
779 }
768 ret = PyDict_New();
780 ret = PyDict_New();
769 if (!ret) {
781 if (!ret) {
770 goto nomem;
782 goto nomem;
771 }
783 }
772 while (sneedle != self->numlines || oneedle != other->numlines) {
784 while (sneedle != self->numlines || oneedle != other->numlines) {
773 line *left = self->lines + sneedle;
785 line *left = self->lines + sneedle;
774 line *right = other->lines + oneedle;
786 line *right = other->lines + oneedle;
775 int result;
787 int result;
776 PyObject *key;
788 PyObject *key;
777 PyObject *outer;
789 PyObject *outer;
778 /* If we're looking at a deleted entry and it's not
790 /* If we're looking at a deleted entry and it's not
779 * the end of the manifest, just skip it. */
791 * the end of the manifest, just skip it. */
780 if (sneedle < self->numlines && left->deleted) {
792 if (sneedle < self->numlines && left->deleted) {
781 sneedle++;
793 sneedle++;
782 continue;
794 continue;
783 }
795 }
784 if (oneedle < other->numlines && right->deleted) {
796 if (oneedle < other->numlines && right->deleted) {
785 oneedle++;
797 oneedle++;
786 continue;
798 continue;
787 }
799 }
788 /* if we're at the end of either manifest, then we
800 /* if we're at the end of either manifest, then we
789 * know the remaining items are adds so we can skip
801 * know the remaining items are adds so we can skip
790 * the strcmp. */
802 * the strcmp. */
791 if (sneedle == self->numlines) {
803 if (sneedle == self->numlines) {
792 result = 1;
804 result = 1;
793 } else if (oneedle == other->numlines) {
805 } else if (oneedle == other->numlines) {
794 result = -1;
806 result = -1;
795 } else {
807 } else {
796 result = linecmp(left, right);
808 result = linecmp(left, right);
797 }
809 }
798 key = result <= 0 ?
810 key = result <= 0 ?
799 PyBytes_FromString(left->start) :
811 PyBytes_FromString(left->start) :
800 PyBytes_FromString(right->start);
812 PyBytes_FromString(right->start);
801 if (!key)
813 if (!key)
802 goto nomem;
814 goto nomem;
803 if (result < 0) {
815 if (result < 0) {
804 PyObject *l = hashflags(left);
816 PyObject *l = hashflags(left);
805 if (!l) {
817 if (!l) {
806 goto nomem;
818 goto nomem;
807 }
819 }
808 outer = PyTuple_Pack(2, l, emptyTup);
820 outer = PyTuple_Pack(2, l, emptyTup);
809 Py_DECREF(l);
821 Py_DECREF(l);
810 if (!outer) {
822 if (!outer) {
811 goto nomem;
823 goto nomem;
812 }
824 }
813 PyDict_SetItem(ret, key, outer);
825 PyDict_SetItem(ret, key, outer);
814 Py_DECREF(outer);
826 Py_DECREF(outer);
815 sneedle++;
827 sneedle++;
816 } else if (result > 0) {
828 } else if (result > 0) {
817 PyObject *r = hashflags(right);
829 PyObject *r = hashflags(right);
818 if (!r) {
830 if (!r) {
819 goto nomem;
831 goto nomem;
820 }
832 }
821 outer = PyTuple_Pack(2, emptyTup, r);
833 outer = PyTuple_Pack(2, emptyTup, r);
822 Py_DECREF(r);
834 Py_DECREF(r);
823 if (!outer) {
835 if (!outer) {
824 goto nomem;
836 goto nomem;
825 }
837 }
826 PyDict_SetItem(ret, key, outer);
838 PyDict_SetItem(ret, key, outer);
827 Py_DECREF(outer);
839 Py_DECREF(outer);
828 oneedle++;
840 oneedle++;
829 } else {
841 } else {
830 /* file exists in both manifests */
842 /* file exists in both manifests */
831 if (left->len != right->len
843 if (left->len != right->len
832 || memcmp(left->start, right->start, left->len)
844 || memcmp(left->start, right->start, left->len)
833 || left->hash_suffix != right->hash_suffix) {
845 || left->hash_suffix != right->hash_suffix) {
834 PyObject *l = hashflags(left);
846 PyObject *l = hashflags(left);
835 PyObject *r;
847 PyObject *r;
836 if (!l) {
848 if (!l) {
837 goto nomem;
849 goto nomem;
838 }
850 }
839 r = hashflags(right);
851 r = hashflags(right);
840 if (!r) {
852 if (!r) {
841 Py_DECREF(l);
853 Py_DECREF(l);
842 goto nomem;
854 goto nomem;
843 }
855 }
844 outer = PyTuple_Pack(2, l, r);
856 outer = PyTuple_Pack(2, l, r);
845 Py_DECREF(l);
857 Py_DECREF(l);
846 Py_DECREF(r);
858 Py_DECREF(r);
847 if (!outer) {
859 if (!outer) {
848 goto nomem;
860 goto nomem;
849 }
861 }
850 PyDict_SetItem(ret, key, outer);
862 PyDict_SetItem(ret, key, outer);
851 Py_DECREF(outer);
863 Py_DECREF(outer);
852 } else if (listclean) {
864 } else if (listclean) {
853 PyDict_SetItem(ret, key, Py_None);
865 PyDict_SetItem(ret, key, Py_None);
854 }
866 }
855 sneedle++;
867 sneedle++;
856 oneedle++;
868 oneedle++;
857 }
869 }
858 Py_DECREF(key);
870 Py_DECREF(key);
859 }
871 }
860 Py_DECREF(emptyTup);
872 Py_DECREF(emptyTup);
861 return ret;
873 return ret;
862 nomem:
874 nomem:
863 PyErr_NoMemory();
875 PyErr_NoMemory();
864 Py_XDECREF(ret);
876 Py_XDECREF(ret);
865 Py_XDECREF(emptyTup);
877 Py_XDECREF(emptyTup);
866 return NULL;
878 return NULL;
867 }
879 }
868
880
869 static PyMethodDef lazymanifest_methods[] = {
881 static PyMethodDef lazymanifest_methods[] = {
870 {"iterkeys", (PyCFunction)lazymanifest_getkeysiter, METH_NOARGS,
882 {"iterkeys", (PyCFunction)lazymanifest_getkeysiter, METH_NOARGS,
871 "Iterate over file names in this lazymanifest."},
883 "Iterate over file names in this lazymanifest."},
872 {"iterentries", (PyCFunction)lazymanifest_getentriesiter, METH_NOARGS,
884 {"iterentries", (PyCFunction)lazymanifest_getentriesiter, METH_NOARGS,
873 "Iterate over (path, nodeid, flags) tuples in this lazymanifest."},
885 "Iterate over (path, nodeid, flags) tuples in this lazymanifest."},
874 {"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS,
886 {"copy", (PyCFunction)lazymanifest_copy, METH_NOARGS,
875 "Make a copy of this lazymanifest."},
887 "Make a copy of this lazymanifest."},
876 {"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O,
888 {"filtercopy", (PyCFunction)lazymanifest_filtercopy, METH_O,
877 "Make a copy of this manifest filtered by matchfn."},
889 "Make a copy of this manifest filtered by matchfn."},
878 {"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS,
890 {"diff", (PyCFunction)lazymanifest_diff, METH_VARARGS,
879 "Compare this lazymanifest to another one."},
891 "Compare this lazymanifest to another one."},
880 {"text", (PyCFunction)lazymanifest_text, METH_NOARGS,
892 {"text", (PyCFunction)lazymanifest_text, METH_NOARGS,
881 "Encode this manifest to text."},
893 "Encode this manifest to text."},
882 {NULL},
894 {NULL},
883 };
895 };
884
896
885 #ifdef IS_PY3K
897 #ifdef IS_PY3K
886 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT
898 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT
887 #else
899 #else
888 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN
900 #define LAZYMANIFEST_TPFLAGS Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_SEQUENCE_IN
889 #endif
901 #endif
890
902
891 static PyTypeObject lazymanifestType = {
903 static PyTypeObject lazymanifestType = {
892 PyVarObject_HEAD_INIT(NULL, 0) /* header */
904 PyVarObject_HEAD_INIT(NULL, 0) /* header */
893 "parsers.lazymanifest", /* tp_name */
905 "parsers.lazymanifest", /* tp_name */
894 sizeof(lazymanifest), /* tp_basicsize */
906 sizeof(lazymanifest), /* tp_basicsize */
895 0, /* tp_itemsize */
907 0, /* tp_itemsize */
896 (destructor)lazymanifest_dealloc, /* tp_dealloc */
908 (destructor)lazymanifest_dealloc, /* tp_dealloc */
897 0, /* tp_print */
909 0, /* tp_print */
898 0, /* tp_getattr */
910 0, /* tp_getattr */
899 0, /* tp_setattr */
911 0, /* tp_setattr */
900 0, /* tp_compare */
912 0, /* tp_compare */
901 0, /* tp_repr */
913 0, /* tp_repr */
902 0, /* tp_as_number */
914 0, /* tp_as_number */
903 &lazymanifest_seq_meths, /* tp_as_sequence */
915 &lazymanifest_seq_meths, /* tp_as_sequence */
904 &lazymanifest_mapping_methods, /* tp_as_mapping */
916 &lazymanifest_mapping_methods, /* tp_as_mapping */
905 0, /* tp_hash */
917 0, /* tp_hash */
906 0, /* tp_call */
918 0, /* tp_call */
907 0, /* tp_str */
919 0, /* tp_str */
908 0, /* tp_getattro */
920 0, /* tp_getattro */
909 0, /* tp_setattro */
921 0, /* tp_setattro */
910 0, /* tp_as_buffer */
922 0, /* tp_as_buffer */
911 LAZYMANIFEST_TPFLAGS, /* tp_flags */
923 LAZYMANIFEST_TPFLAGS, /* tp_flags */
912 "TODO(augie)", /* tp_doc */
924 "TODO(augie)", /* tp_doc */
913 0, /* tp_traverse */
925 0, /* tp_traverse */
914 0, /* tp_clear */
926 0, /* tp_clear */
915 0, /* tp_richcompare */
927 0, /* tp_richcompare */
916 0, /* tp_weaklistoffset */
928 0, /* tp_weaklistoffset */
917 (getiterfunc)lazymanifest_getkeysiter, /* tp_iter */
929 (getiterfunc)lazymanifest_getkeysiter, /* tp_iter */
918 0, /* tp_iternext */
930 0, /* tp_iternext */
919 lazymanifest_methods, /* tp_methods */
931 lazymanifest_methods, /* tp_methods */
920 0, /* tp_members */
932 0, /* tp_members */
921 0, /* tp_getset */
933 0, /* tp_getset */
922 0, /* tp_base */
934 0, /* tp_base */
923 0, /* tp_dict */
935 0, /* tp_dict */
924 0, /* tp_descr_get */
936 0, /* tp_descr_get */
925 0, /* tp_descr_set */
937 0, /* tp_descr_set */
926 0, /* tp_dictoffset */
938 0, /* tp_dictoffset */
927 (initproc)lazymanifest_init, /* tp_init */
939 (initproc)lazymanifest_init, /* tp_init */
928 0, /* tp_alloc */
940 0, /* tp_alloc */
929 };
941 };
930
942
931 void manifest_module_init(PyObject * mod)
943 void manifest_module_init(PyObject * mod)
932 {
944 {
933 lazymanifestType.tp_new = PyType_GenericNew;
945 lazymanifestType.tp_new = PyType_GenericNew;
934 if (PyType_Ready(&lazymanifestType) < 0)
946 if (PyType_Ready(&lazymanifestType) < 0)
935 return;
947 return;
936 Py_INCREF(&lazymanifestType);
948 Py_INCREF(&lazymanifestType);
937
949
938 PyModule_AddObject(mod, "lazymanifest",
950 PyModule_AddObject(mod, "lazymanifest",
939 (PyObject *)&lazymanifestType);
951 (PyObject *)&lazymanifestType);
940 }
952 }
@@ -1,1784 +1,1784 b''
1 # stuff related specifically to patch manipulation / parsing
1 # stuff related specifically to patch manipulation / parsing
2 #
2 #
3 # Copyright 2008 Mark Edgington <edgimar@gmail.com>
3 # Copyright 2008 Mark Edgington <edgimar@gmail.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7 #
7 #
8 # This code is based on the Mark Edgington's crecord extension.
8 # This code is based on the Mark Edgington's crecord extension.
9 # (Itself based on Bryan O'Sullivan's record extension.)
9 # (Itself based on Bryan O'Sullivan's record extension.)
10
10
11 from __future__ import absolute_import
11 from __future__ import absolute_import
12
12
13 import locale
13 import locale
14 import os
14 import os
15 import re
15 import re
16 import signal
16 import signal
17
17
18 from .i18n import _
18 from .i18n import _
19 from . import (
19 from . import (
20 encoding,
20 encoding,
21 error,
21 error,
22 patch as patchmod,
22 patch as patchmod,
23 scmutil,
23 scmutil,
24 util,
24 util,
25 )
25 )
26 from .utils import (
26 from .utils import (
27 stringutil,
27 stringutil,
28 )
28 )
29 stringio = util.stringio
29 stringio = util.stringio
30
30
31 # This is required for ncurses to display non-ASCII characters in default user
31 # This is required for ncurses to display non-ASCII characters in default user
32 # locale encoding correctly. --immerrr
32 # locale encoding correctly. --immerrr
33 locale.setlocale(locale.LC_ALL, u'')
33 locale.setlocale(locale.LC_ALL, u'')
34
34
35 # patch comments based on the git one
35 # patch comments based on the git one
36 diffhelptext = _("""# To remove '-' lines, make them ' ' lines (context).
36 diffhelptext = _("""# To remove '-' lines, make them ' ' lines (context).
37 # To remove '+' lines, delete them.
37 # To remove '+' lines, delete them.
38 # Lines starting with # will be removed from the patch.
38 # Lines starting with # will be removed from the patch.
39 """)
39 """)
40
40
41 hunkhelptext = _("""#
41 hunkhelptext = _("""#
42 # If the patch applies cleanly, the edited hunk will immediately be
42 # If the patch applies cleanly, the edited hunk will immediately be
43 # added to the record list. If it does not apply cleanly, a rejects file
43 # added to the record list. If it does not apply cleanly, a rejects file
44 # will be generated. You can use that when you try again. If all lines
44 # will be generated. You can use that when you try again. If all lines
45 # of the hunk are removed, then the edit is aborted and the hunk is left
45 # of the hunk are removed, then the edit is aborted and the hunk is left
46 # unchanged.
46 # unchanged.
47 """)
47 """)
48
48
49 patchhelptext = _("""#
49 patchhelptext = _("""#
50 # If the patch applies cleanly, the edited patch will immediately
50 # If the patch applies cleanly, the edited patch will immediately
51 # be finalised. If it does not apply cleanly, rejects files will be
51 # be finalised. If it does not apply cleanly, rejects files will be
52 # generated. You can use those when you try again.
52 # generated. You can use those when you try again.
53 """)
53 """)
54
54
55 try:
55 try:
56 import curses
56 import curses
57 curses.error
57 curses.error
58 except ImportError:
58 except ImportError:
59 # I have no idea if wcurses works with crecord...
59 # I have no idea if wcurses works with crecord...
60 try:
60 try:
61 import wcurses as curses
61 import wcurses as curses
62 curses.error
62 curses.error
63 except ImportError:
63 except ImportError:
64 # wcurses is not shipped on Windows by default, or python is not
64 # wcurses is not shipped on Windows by default, or python is not
65 # compiled with curses
65 # compiled with curses
66 curses = False
66 curses = False
67
67
68 class fallbackerror(error.Abort):
68 class fallbackerror(error.Abort):
69 """Error that indicates the client should try to fallback to text mode."""
69 """Error that indicates the client should try to fallback to text mode."""
70 # Inherits from error.Abort so that existing behavior is preserved if the
70 # Inherits from error.Abort so that existing behavior is preserved if the
71 # calling code does not know how to fallback.
71 # calling code does not know how to fallback.
72
72
73 def checkcurses(ui):
73 def checkcurses(ui):
74 """Return True if the user wants to use curses
74 """Return True if the user wants to use curses
75
75
76 This method returns True if curses is found (and that python is built with
76 This method returns True if curses is found (and that python is built with
77 it) and that the user has the correct flag for the ui.
77 it) and that the user has the correct flag for the ui.
78 """
78 """
79 return curses and ui.interface("chunkselector") == "curses"
79 return curses and ui.interface("chunkselector") == "curses"
80
80
81 class patchnode(object):
81 class patchnode(object):
82 """abstract class for patch graph nodes
82 """abstract class for patch graph nodes
83 (i.e. patchroot, header, hunk, hunkline)
83 (i.e. patchroot, header, hunk, hunkline)
84 """
84 """
85
85
86 def firstchild(self):
86 def firstchild(self):
87 raise NotImplementedError("method must be implemented by subclass")
87 raise NotImplementedError("method must be implemented by subclass")
88
88
89 def lastchild(self):
89 def lastchild(self):
90 raise NotImplementedError("method must be implemented by subclass")
90 raise NotImplementedError("method must be implemented by subclass")
91
91
92 def allchildren(self):
92 def allchildren(self):
93 "Return a list of all of the direct children of this node"
93 "Return a list of all of the direct children of this node"
94 raise NotImplementedError("method must be implemented by subclass")
94 raise NotImplementedError("method must be implemented by subclass")
95
95
96 def nextsibling(self):
96 def nextsibling(self):
97 """
97 """
98 Return the closest next item of the same type where there are no items
98 Return the closest next item of the same type where there are no items
99 of different types between the current item and this closest item.
99 of different types between the current item and this closest item.
100 If no such item exists, return None.
100 If no such item exists, return None.
101 """
101 """
102 raise NotImplementedError("method must be implemented by subclass")
102 raise NotImplementedError("method must be implemented by subclass")
103
103
104 def prevsibling(self):
104 def prevsibling(self):
105 """
105 """
106 Return the closest previous item of the same type where there are no
106 Return the closest previous item of the same type where there are no
107 items of different types between the current item and this closest item.
107 items of different types between the current item and this closest item.
108 If no such item exists, return None.
108 If no such item exists, return None.
109 """
109 """
110 raise NotImplementedError("method must be implemented by subclass")
110 raise NotImplementedError("method must be implemented by subclass")
111
111
112 def parentitem(self):
112 def parentitem(self):
113 raise NotImplementedError("method must be implemented by subclass")
113 raise NotImplementedError("method must be implemented by subclass")
114
114
115 def nextitem(self, skipfolded=True):
115 def nextitem(self, skipfolded=True):
116 """
116 """
117 Try to return the next item closest to this item, regardless of item's
117 Try to return the next item closest to this item, regardless of item's
118 type (header, hunk, or hunkline).
118 type (header, hunk, or hunkline).
119
119
120 If skipfolded == True, and the current item is folded, then the child
120 If skipfolded == True, and the current item is folded, then the child
121 items that are hidden due to folding will be skipped when determining
121 items that are hidden due to folding will be skipped when determining
122 the next item.
122 the next item.
123
123
124 If it is not possible to get the next item, return None.
124 If it is not possible to get the next item, return None.
125 """
125 """
126 try:
126 try:
127 itemfolded = self.folded
127 itemfolded = self.folded
128 except AttributeError:
128 except AttributeError:
129 itemfolded = False
129 itemfolded = False
130 if skipfolded and itemfolded:
130 if skipfolded and itemfolded:
131 nextitem = self.nextsibling()
131 nextitem = self.nextsibling()
132 if nextitem is None:
132 if nextitem is None:
133 try:
133 try:
134 nextitem = self.parentitem().nextsibling()
134 nextitem = self.parentitem().nextsibling()
135 except AttributeError:
135 except AttributeError:
136 nextitem = None
136 nextitem = None
137 return nextitem
137 return nextitem
138 else:
138 else:
139 # try child
139 # try child
140 item = self.firstchild()
140 item = self.firstchild()
141 if item is not None:
141 if item is not None:
142 return item
142 return item
143
143
144 # else try next sibling
144 # else try next sibling
145 item = self.nextsibling()
145 item = self.nextsibling()
146 if item is not None:
146 if item is not None:
147 return item
147 return item
148
148
149 try:
149 try:
150 # else try parent's next sibling
150 # else try parent's next sibling
151 item = self.parentitem().nextsibling()
151 item = self.parentitem().nextsibling()
152 if item is not None:
152 if item is not None:
153 return item
153 return item
154
154
155 # else return grandparent's next sibling (or None)
155 # else return grandparent's next sibling (or None)
156 return self.parentitem().parentitem().nextsibling()
156 return self.parentitem().parentitem().nextsibling()
157
157
158 except AttributeError: # parent and/or grandparent was None
158 except AttributeError: # parent and/or grandparent was None
159 return None
159 return None
160
160
161 def previtem(self):
161 def previtem(self):
162 """
162 """
163 Try to return the previous item closest to this item, regardless of
163 Try to return the previous item closest to this item, regardless of
164 item's type (header, hunk, or hunkline).
164 item's type (header, hunk, or hunkline).
165
165
166 If it is not possible to get the previous item, return None.
166 If it is not possible to get the previous item, return None.
167 """
167 """
168 # try previous sibling's last child's last child,
168 # try previous sibling's last child's last child,
169 # else try previous sibling's last child, else try previous sibling
169 # else try previous sibling's last child, else try previous sibling
170 prevsibling = self.prevsibling()
170 prevsibling = self.prevsibling()
171 if prevsibling is not None:
171 if prevsibling is not None:
172 prevsiblinglastchild = prevsibling.lastchild()
172 prevsiblinglastchild = prevsibling.lastchild()
173 if ((prevsiblinglastchild is not None) and
173 if ((prevsiblinglastchild is not None) and
174 not prevsibling.folded):
174 not prevsibling.folded):
175 prevsiblinglclc = prevsiblinglastchild.lastchild()
175 prevsiblinglclc = prevsiblinglastchild.lastchild()
176 if ((prevsiblinglclc is not None) and
176 if ((prevsiblinglclc is not None) and
177 not prevsiblinglastchild.folded):
177 not prevsiblinglastchild.folded):
178 return prevsiblinglclc
178 return prevsiblinglclc
179 else:
179 else:
180 return prevsiblinglastchild
180 return prevsiblinglastchild
181 else:
181 else:
182 return prevsibling
182 return prevsibling
183
183
184 # try parent (or None)
184 # try parent (or None)
185 return self.parentitem()
185 return self.parentitem()
186
186
187 class patch(patchnode, list): # todo: rename patchroot
187 class patch(patchnode, list): # todo: rename patchroot
188 """
188 """
189 list of header objects representing the patch.
189 list of header objects representing the patch.
190 """
190 """
191 def __init__(self, headerlist):
191 def __init__(self, headerlist):
192 self.extend(headerlist)
192 self.extend(headerlist)
193 # add parent patch object reference to each header
193 # add parent patch object reference to each header
194 for header in self:
194 for header in self:
195 header.patch = self
195 header.patch = self
196
196
197 class uiheader(patchnode):
197 class uiheader(patchnode):
198 """patch header
198 """patch header
199
199
200 xxx shouldn't we move this to mercurial/patch.py ?
200 xxx shouldn't we move this to mercurial/patch.py ?
201 """
201 """
202
202
203 def __init__(self, header):
203 def __init__(self, header):
204 self.nonuiheader = header
204 self.nonuiheader = header
205 # flag to indicate whether to apply this chunk
205 # flag to indicate whether to apply this chunk
206 self.applied = True
206 self.applied = True
207 # flag which only affects the status display indicating if a node's
207 # flag which only affects the status display indicating if a node's
208 # children are partially applied (i.e. some applied, some not).
208 # children are partially applied (i.e. some applied, some not).
209 self.partial = False
209 self.partial = False
210
210
211 # flag to indicate whether to display as folded/unfolded to user
211 # flag to indicate whether to display as folded/unfolded to user
212 self.folded = True
212 self.folded = True
213
213
214 # list of all headers in patch
214 # list of all headers in patch
215 self.patch = None
215 self.patch = None
216
216
217 # flag is False if this header was ever unfolded from initial state
217 # flag is False if this header was ever unfolded from initial state
218 self.neverunfolded = True
218 self.neverunfolded = True
219 self.hunks = [uihunk(h, self) for h in self.hunks]
219 self.hunks = [uihunk(h, self) for h in self.hunks]
220
220
221 def prettystr(self):
221 def prettystr(self):
222 x = stringio()
222 x = stringio()
223 self.pretty(x)
223 self.pretty(x)
224 return x.getvalue()
224 return x.getvalue()
225
225
226 def nextsibling(self):
226 def nextsibling(self):
227 numheadersinpatch = len(self.patch)
227 numheadersinpatch = len(self.patch)
228 indexofthisheader = self.patch.index(self)
228 indexofthisheader = self.patch.index(self)
229
229
230 if indexofthisheader < numheadersinpatch - 1:
230 if indexofthisheader < numheadersinpatch - 1:
231 nextheader = self.patch[indexofthisheader + 1]
231 nextheader = self.patch[indexofthisheader + 1]
232 return nextheader
232 return nextheader
233 else:
233 else:
234 return None
234 return None
235
235
236 def prevsibling(self):
236 def prevsibling(self):
237 indexofthisheader = self.patch.index(self)
237 indexofthisheader = self.patch.index(self)
238 if indexofthisheader > 0:
238 if indexofthisheader > 0:
239 previousheader = self.patch[indexofthisheader - 1]
239 previousheader = self.patch[indexofthisheader - 1]
240 return previousheader
240 return previousheader
241 else:
241 else:
242 return None
242 return None
243
243
244 def parentitem(self):
244 def parentitem(self):
245 """
245 """
246 there is no 'real' parent item of a header that can be selected,
246 there is no 'real' parent item of a header that can be selected,
247 so return None.
247 so return None.
248 """
248 """
249 return None
249 return None
250
250
251 def firstchild(self):
251 def firstchild(self):
252 "return the first child of this item, if one exists. otherwise None."
252 "return the first child of this item, if one exists. otherwise None."
253 if len(self.hunks) > 0:
253 if len(self.hunks) > 0:
254 return self.hunks[0]
254 return self.hunks[0]
255 else:
255 else:
256 return None
256 return None
257
257
258 def lastchild(self):
258 def lastchild(self):
259 "return the last child of this item, if one exists. otherwise None."
259 "return the last child of this item, if one exists. otherwise None."
260 if len(self.hunks) > 0:
260 if len(self.hunks) > 0:
261 return self.hunks[-1]
261 return self.hunks[-1]
262 else:
262 else:
263 return None
263 return None
264
264
265 def allchildren(self):
265 def allchildren(self):
266 "return a list of all of the direct children of this node"
266 "return a list of all of the direct children of this node"
267 return self.hunks
267 return self.hunks
268
268
269 def __getattr__(self, name):
269 def __getattr__(self, name):
270 return getattr(self.nonuiheader, name)
270 return getattr(self.nonuiheader, name)
271
271
272 class uihunkline(patchnode):
272 class uihunkline(patchnode):
273 "represents a changed line in a hunk"
273 "represents a changed line in a hunk"
274 def __init__(self, linetext, hunk):
274 def __init__(self, linetext, hunk):
275 self.linetext = linetext
275 self.linetext = linetext
276 self.applied = True
276 self.applied = True
277 # the parent hunk to which this line belongs
277 # the parent hunk to which this line belongs
278 self.hunk = hunk
278 self.hunk = hunk
279 # folding lines currently is not used/needed, but this flag is needed
279 # folding lines currently is not used/needed, but this flag is needed
280 # in the previtem method.
280 # in the previtem method.
281 self.folded = False
281 self.folded = False
282
282
283 def prettystr(self):
283 def prettystr(self):
284 return self.linetext
284 return self.linetext
285
285
286 def nextsibling(self):
286 def nextsibling(self):
287 numlinesinhunk = len(self.hunk.changedlines)
287 numlinesinhunk = len(self.hunk.changedlines)
288 indexofthisline = self.hunk.changedlines.index(self)
288 indexofthisline = self.hunk.changedlines.index(self)
289
289
290 if (indexofthisline < numlinesinhunk - 1):
290 if (indexofthisline < numlinesinhunk - 1):
291 nextline = self.hunk.changedlines[indexofthisline + 1]
291 nextline = self.hunk.changedlines[indexofthisline + 1]
292 return nextline
292 return nextline
293 else:
293 else:
294 return None
294 return None
295
295
296 def prevsibling(self):
296 def prevsibling(self):
297 indexofthisline = self.hunk.changedlines.index(self)
297 indexofthisline = self.hunk.changedlines.index(self)
298 if indexofthisline > 0:
298 if indexofthisline > 0:
299 previousline = self.hunk.changedlines[indexofthisline - 1]
299 previousline = self.hunk.changedlines[indexofthisline - 1]
300 return previousline
300 return previousline
301 else:
301 else:
302 return None
302 return None
303
303
304 def parentitem(self):
304 def parentitem(self):
305 "return the parent to the current item"
305 "return the parent to the current item"
306 return self.hunk
306 return self.hunk
307
307
308 def firstchild(self):
308 def firstchild(self):
309 "return the first child of this item, if one exists. otherwise None."
309 "return the first child of this item, if one exists. otherwise None."
310 # hunk-lines don't have children
310 # hunk-lines don't have children
311 return None
311 return None
312
312
313 def lastchild(self):
313 def lastchild(self):
314 "return the last child of this item, if one exists. otherwise None."
314 "return the last child of this item, if one exists. otherwise None."
315 # hunk-lines don't have children
315 # hunk-lines don't have children
316 return None
316 return None
317
317
318 class uihunk(patchnode):
318 class uihunk(patchnode):
319 """ui patch hunk, wraps a hunk and keep track of ui behavior """
319 """ui patch hunk, wraps a hunk and keep track of ui behavior """
320 maxcontext = 3
320 maxcontext = 3
321
321
322 def __init__(self, hunk, header):
322 def __init__(self, hunk, header):
323 self._hunk = hunk
323 self._hunk = hunk
324 self.changedlines = [uihunkline(line, self) for line in hunk.hunk]
324 self.changedlines = [uihunkline(line, self) for line in hunk.hunk]
325 self.header = header
325 self.header = header
326 # used at end for detecting how many removed lines were un-applied
326 # used at end for detecting how many removed lines were un-applied
327 self.originalremoved = self.removed
327 self.originalremoved = self.removed
328
328
329 # flag to indicate whether to display as folded/unfolded to user
329 # flag to indicate whether to display as folded/unfolded to user
330 self.folded = True
330 self.folded = True
331 # flag to indicate whether to apply this chunk
331 # flag to indicate whether to apply this chunk
332 self.applied = True
332 self.applied = True
333 # flag which only affects the status display indicating if a node's
333 # flag which only affects the status display indicating if a node's
334 # children are partially applied (i.e. some applied, some not).
334 # children are partially applied (i.e. some applied, some not).
335 self.partial = False
335 self.partial = False
336
336
337 def nextsibling(self):
337 def nextsibling(self):
338 numhunksinheader = len(self.header.hunks)
338 numhunksinheader = len(self.header.hunks)
339 indexofthishunk = self.header.hunks.index(self)
339 indexofthishunk = self.header.hunks.index(self)
340
340
341 if (indexofthishunk < numhunksinheader - 1):
341 if (indexofthishunk < numhunksinheader - 1):
342 nexthunk = self.header.hunks[indexofthishunk + 1]
342 nexthunk = self.header.hunks[indexofthishunk + 1]
343 return nexthunk
343 return nexthunk
344 else:
344 else:
345 return None
345 return None
346
346
347 def prevsibling(self):
347 def prevsibling(self):
348 indexofthishunk = self.header.hunks.index(self)
348 indexofthishunk = self.header.hunks.index(self)
349 if indexofthishunk > 0:
349 if indexofthishunk > 0:
350 previoushunk = self.header.hunks[indexofthishunk - 1]
350 previoushunk = self.header.hunks[indexofthishunk - 1]
351 return previoushunk
351 return previoushunk
352 else:
352 else:
353 return None
353 return None
354
354
355 def parentitem(self):
355 def parentitem(self):
356 "return the parent to the current item"
356 "return the parent to the current item"
357 return self.header
357 return self.header
358
358
359 def firstchild(self):
359 def firstchild(self):
360 "return the first child of this item, if one exists. otherwise None."
360 "return the first child of this item, if one exists. otherwise None."
361 if len(self.changedlines) > 0:
361 if len(self.changedlines) > 0:
362 return self.changedlines[0]
362 return self.changedlines[0]
363 else:
363 else:
364 return None
364 return None
365
365
366 def lastchild(self):
366 def lastchild(self):
367 "return the last child of this item, if one exists. otherwise None."
367 "return the last child of this item, if one exists. otherwise None."
368 if len(self.changedlines) > 0:
368 if len(self.changedlines) > 0:
369 return self.changedlines[-1]
369 return self.changedlines[-1]
370 else:
370 else:
371 return None
371 return None
372
372
373 def allchildren(self):
373 def allchildren(self):
374 "return a list of all of the direct children of this node"
374 "return a list of all of the direct children of this node"
375 return self.changedlines
375 return self.changedlines
376
376
377 def countchanges(self):
377 def countchanges(self):
378 """changedlines -> (n+,n-)"""
378 """changedlines -> (n+,n-)"""
379 add = len([l for l in self.changedlines if l.applied
379 add = len([l for l in self.changedlines if l.applied
380 and l.prettystr()[0] == '+'])
380 and l.prettystr()[0] == '+'])
381 rem = len([l for l in self.changedlines if l.applied
381 rem = len([l for l in self.changedlines if l.applied
382 and l.prettystr()[0] == '-'])
382 and l.prettystr()[0] == '-'])
383 return add, rem
383 return add, rem
384
384
385 def getfromtoline(self):
385 def getfromtoline(self):
386 # calculate the number of removed lines converted to context lines
386 # calculate the number of removed lines converted to context lines
387 removedconvertedtocontext = self.originalremoved - self.removed
387 removedconvertedtocontext = self.originalremoved - self.removed
388
388
389 contextlen = (len(self.before) + len(self.after) +
389 contextlen = (len(self.before) + len(self.after) +
390 removedconvertedtocontext)
390 removedconvertedtocontext)
391 if self.after and self.after[-1] == '\\ No newline at end of file\n':
391 if self.after and self.after[-1] == '\\ No newline at end of file\n':
392 contextlen -= 1
392 contextlen -= 1
393 fromlen = contextlen + self.removed
393 fromlen = contextlen + self.removed
394 tolen = contextlen + self.added
394 tolen = contextlen + self.added
395
395
396 # diffutils manual, section "2.2.2.2 detailed description of unified
396 # diffutils manual, section "2.2.2.2 detailed description of unified
397 # format": "an empty hunk is considered to end at the line that
397 # format": "an empty hunk is considered to end at the line that
398 # precedes the hunk."
398 # precedes the hunk."
399 #
399 #
400 # so, if either of hunks is empty, decrease its line start. --immerrr
400 # so, if either of hunks is empty, decrease its line start. --immerrr
401 # but only do this if fromline > 0, to avoid having, e.g fromline=-1.
401 # but only do this if fromline > 0, to avoid having, e.g fromline=-1.
402 fromline, toline = self.fromline, self.toline
402 fromline, toline = self.fromline, self.toline
403 if fromline != 0:
403 if fromline != 0:
404 if fromlen == 0:
404 if fromlen == 0:
405 fromline -= 1
405 fromline -= 1
406 if tolen == 0:
406 if tolen == 0 and toline > 0:
407 toline -= 1
407 toline -= 1
408
408
409 fromtoline = '@@ -%d,%d +%d,%d @@%s\n' % (
409 fromtoline = '@@ -%d,%d +%d,%d @@%s\n' % (
410 fromline, fromlen, toline, tolen,
410 fromline, fromlen, toline, tolen,
411 self.proc and (' ' + self.proc))
411 self.proc and (' ' + self.proc))
412 return fromtoline
412 return fromtoline
413
413
414 def write(self, fp):
414 def write(self, fp):
415 # updated self.added/removed, which are used by getfromtoline()
415 # updated self.added/removed, which are used by getfromtoline()
416 self.added, self.removed = self.countchanges()
416 self.added, self.removed = self.countchanges()
417 fp.write(self.getfromtoline())
417 fp.write(self.getfromtoline())
418
418
419 hunklinelist = []
419 hunklinelist = []
420 # add the following to the list: (1) all applied lines, and
420 # add the following to the list: (1) all applied lines, and
421 # (2) all unapplied removal lines (convert these to context lines)
421 # (2) all unapplied removal lines (convert these to context lines)
422 for changedline in self.changedlines:
422 for changedline in self.changedlines:
423 changedlinestr = changedline.prettystr()
423 changedlinestr = changedline.prettystr()
424 if changedline.applied:
424 if changedline.applied:
425 hunklinelist.append(changedlinestr)
425 hunklinelist.append(changedlinestr)
426 elif changedlinestr[0] == "-":
426 elif changedlinestr[0] == "-":
427 hunklinelist.append(" " + changedlinestr[1:])
427 hunklinelist.append(" " + changedlinestr[1:])
428
428
429 fp.write(''.join(self.before + hunklinelist + self.after))
429 fp.write(''.join(self.before + hunklinelist + self.after))
430
430
431 pretty = write
431 pretty = write
432
432
433 def prettystr(self):
433 def prettystr(self):
434 x = stringio()
434 x = stringio()
435 self.pretty(x)
435 self.pretty(x)
436 return x.getvalue()
436 return x.getvalue()
437
437
438 def reversehunk(self):
438 def reversehunk(self):
439 """return a recordhunk which is the reverse of the hunk
439 """return a recordhunk which is the reverse of the hunk
440
440
441 Assuming the displayed patch is diff(A, B) result. The returned hunk is
441 Assuming the displayed patch is diff(A, B) result. The returned hunk is
442 intended to be applied to B, instead of A.
442 intended to be applied to B, instead of A.
443
443
444 For example, when A is "0\n1\n2\n6\n" and B is "0\n3\n4\n5\n6\n", and
444 For example, when A is "0\n1\n2\n6\n" and B is "0\n3\n4\n5\n6\n", and
445 the user made the following selection:
445 the user made the following selection:
446
446
447 0
447 0
448 [x] -1 [x]: selected
448 [x] -1 [x]: selected
449 [ ] -2 [ ]: not selected
449 [ ] -2 [ ]: not selected
450 [x] +3
450 [x] +3
451 [ ] +4
451 [ ] +4
452 [x] +5
452 [x] +5
453 6
453 6
454
454
455 This function returns a hunk like:
455 This function returns a hunk like:
456
456
457 0
457 0
458 -3
458 -3
459 -4
459 -4
460 -5
460 -5
461 +1
461 +1
462 +4
462 +4
463 6
463 6
464
464
465 Note "4" was first deleted then added. That's because "4" exists in B
465 Note "4" was first deleted then added. That's because "4" exists in B
466 side and "-4" must exist between "-3" and "-5" to make the patch
466 side and "-4" must exist between "-3" and "-5" to make the patch
467 applicable to B.
467 applicable to B.
468 """
468 """
469 dels = []
469 dels = []
470 adds = []
470 adds = []
471 for line in self.changedlines:
471 for line in self.changedlines:
472 text = line.linetext
472 text = line.linetext
473 if line.applied:
473 if line.applied:
474 if text[0] == '+':
474 if text[0] == '+':
475 dels.append(text[1:])
475 dels.append(text[1:])
476 elif text[0] == '-':
476 elif text[0] == '-':
477 adds.append(text[1:])
477 adds.append(text[1:])
478 elif text[0] == '+':
478 elif text[0] == '+':
479 dels.append(text[1:])
479 dels.append(text[1:])
480 adds.append(text[1:])
480 adds.append(text[1:])
481 hunk = ['-%s' % l for l in dels] + ['+%s' % l for l in adds]
481 hunk = ['-%s' % l for l in dels] + ['+%s' % l for l in adds]
482 h = self._hunk
482 h = self._hunk
483 return patchmod.recordhunk(h.header, h.toline, h.fromline, h.proc,
483 return patchmod.recordhunk(h.header, h.toline, h.fromline, h.proc,
484 h.before, hunk, h.after)
484 h.before, hunk, h.after)
485
485
486 def __getattr__(self, name):
486 def __getattr__(self, name):
487 return getattr(self._hunk, name)
487 return getattr(self._hunk, name)
488
488
489 def __repr__(self):
489 def __repr__(self):
490 return '<hunk %r@%d>' % (self.filename(), self.fromline)
490 return '<hunk %r@%d>' % (self.filename(), self.fromline)
491
491
492 def filterpatch(ui, chunks, chunkselector, operation=None):
492 def filterpatch(ui, chunks, chunkselector, operation=None):
493 """interactively filter patch chunks into applied-only chunks"""
493 """interactively filter patch chunks into applied-only chunks"""
494 chunks = list(chunks)
494 chunks = list(chunks)
495 # convert chunks list into structure suitable for displaying/modifying
495 # convert chunks list into structure suitable for displaying/modifying
496 # with curses. create a list of headers only.
496 # with curses. create a list of headers only.
497 headers = [c for c in chunks if isinstance(c, patchmod.header)]
497 headers = [c for c in chunks if isinstance(c, patchmod.header)]
498
498
499 # if there are no changed files
499 # if there are no changed files
500 if len(headers) == 0:
500 if len(headers) == 0:
501 return [], {}
501 return [], {}
502 uiheaders = [uiheader(h) for h in headers]
502 uiheaders = [uiheader(h) for h in headers]
503 # let user choose headers/hunks/lines, and mark their applied flags
503 # let user choose headers/hunks/lines, and mark their applied flags
504 # accordingly
504 # accordingly
505 ret = chunkselector(ui, uiheaders, operation=operation)
505 ret = chunkselector(ui, uiheaders, operation=operation)
506 appliedhunklist = []
506 appliedhunklist = []
507 for hdr in uiheaders:
507 for hdr in uiheaders:
508 if (hdr.applied and
508 if (hdr.applied and
509 (hdr.special() or len([h for h in hdr.hunks if h.applied]) > 0)):
509 (hdr.special() or len([h for h in hdr.hunks if h.applied]) > 0)):
510 appliedhunklist.append(hdr)
510 appliedhunklist.append(hdr)
511 fixoffset = 0
511 fixoffset = 0
512 for hnk in hdr.hunks:
512 for hnk in hdr.hunks:
513 if hnk.applied:
513 if hnk.applied:
514 appliedhunklist.append(hnk)
514 appliedhunklist.append(hnk)
515 # adjust the 'to'-line offset of the hunk to be correct
515 # adjust the 'to'-line offset of the hunk to be correct
516 # after de-activating some of the other hunks for this file
516 # after de-activating some of the other hunks for this file
517 if fixoffset:
517 if fixoffset:
518 #hnk = copy.copy(hnk) # necessary??
518 #hnk = copy.copy(hnk) # necessary??
519 hnk.toline += fixoffset
519 hnk.toline += fixoffset
520 else:
520 else:
521 fixoffset += hnk.removed - hnk.added
521 fixoffset += hnk.removed - hnk.added
522
522
523 return (appliedhunklist, ret)
523 return (appliedhunklist, ret)
524
524
525 def chunkselector(ui, headerlist, operation=None):
525 def chunkselector(ui, headerlist, operation=None):
526 """
526 """
527 curses interface to get selection of chunks, and mark the applied flags
527 curses interface to get selection of chunks, and mark the applied flags
528 of the chosen chunks.
528 of the chosen chunks.
529 """
529 """
530 ui.write(_('starting interactive selection\n'))
530 ui.write(_('starting interactive selection\n'))
531 chunkselector = curseschunkselector(headerlist, ui, operation)
531 chunkselector = curseschunkselector(headerlist, ui, operation)
532 origsigtstp = sentinel = object()
532 origsigtstp = sentinel = object()
533 if util.safehasattr(signal, 'SIGTSTP'):
533 if util.safehasattr(signal, 'SIGTSTP'):
534 origsigtstp = signal.getsignal(signal.SIGTSTP)
534 origsigtstp = signal.getsignal(signal.SIGTSTP)
535 try:
535 try:
536 curses.wrapper(chunkselector.main)
536 curses.wrapper(chunkselector.main)
537 if chunkselector.initexc is not None:
537 if chunkselector.initexc is not None:
538 raise chunkselector.initexc
538 raise chunkselector.initexc
539 # ncurses does not restore signal handler for SIGTSTP
539 # ncurses does not restore signal handler for SIGTSTP
540 finally:
540 finally:
541 if origsigtstp is not sentinel:
541 if origsigtstp is not sentinel:
542 signal.signal(signal.SIGTSTP, origsigtstp)
542 signal.signal(signal.SIGTSTP, origsigtstp)
543 return chunkselector.opts
543 return chunkselector.opts
544
544
545 def testdecorator(testfn, f):
545 def testdecorator(testfn, f):
546 def u(*args, **kwargs):
546 def u(*args, **kwargs):
547 return f(testfn, *args, **kwargs)
547 return f(testfn, *args, **kwargs)
548 return u
548 return u
549
549
550 def testchunkselector(testfn, ui, headerlist, operation=None):
550 def testchunkselector(testfn, ui, headerlist, operation=None):
551 """
551 """
552 test interface to get selection of chunks, and mark the applied flags
552 test interface to get selection of chunks, and mark the applied flags
553 of the chosen chunks.
553 of the chosen chunks.
554 """
554 """
555 chunkselector = curseschunkselector(headerlist, ui, operation)
555 chunkselector = curseschunkselector(headerlist, ui, operation)
556 if testfn and os.path.exists(testfn):
556 if testfn and os.path.exists(testfn):
557 testf = open(testfn, 'rb')
557 testf = open(testfn, 'rb')
558 testcommands = [x.rstrip('\n') for x in testf.readlines()]
558 testcommands = [x.rstrip('\n') for x in testf.readlines()]
559 testf.close()
559 testf.close()
560 while True:
560 while True:
561 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
561 if chunkselector.handlekeypressed(testcommands.pop(0), test=True):
562 break
562 break
563 return chunkselector.opts
563 return chunkselector.opts
564
564
565 _headermessages = { # {operation: text}
565 _headermessages = { # {operation: text}
566 'apply': _('Select hunks to apply'),
566 'apply': _('Select hunks to apply'),
567 'discard': _('Select hunks to discard'),
567 'discard': _('Select hunks to discard'),
568 None: _('Select hunks to record'),
568 None: _('Select hunks to record'),
569 }
569 }
570
570
571 class curseschunkselector(object):
571 class curseschunkselector(object):
572 def __init__(self, headerlist, ui, operation=None):
572 def __init__(self, headerlist, ui, operation=None):
573 # put the headers into a patch object
573 # put the headers into a patch object
574 self.headerlist = patch(headerlist)
574 self.headerlist = patch(headerlist)
575
575
576 self.ui = ui
576 self.ui = ui
577 self.opts = {}
577 self.opts = {}
578
578
579 self.errorstr = None
579 self.errorstr = None
580 # list of all chunks
580 # list of all chunks
581 self.chunklist = []
581 self.chunklist = []
582 for h in headerlist:
582 for h in headerlist:
583 self.chunklist.append(h)
583 self.chunklist.append(h)
584 self.chunklist.extend(h.hunks)
584 self.chunklist.extend(h.hunks)
585
585
586 # dictionary mapping (fgcolor, bgcolor) pairs to the
586 # dictionary mapping (fgcolor, bgcolor) pairs to the
587 # corresponding curses color-pair value.
587 # corresponding curses color-pair value.
588 self.colorpairs = {}
588 self.colorpairs = {}
589 # maps custom nicknames of color-pairs to curses color-pair values
589 # maps custom nicknames of color-pairs to curses color-pair values
590 self.colorpairnames = {}
590 self.colorpairnames = {}
591
591
592 # Honor color setting of ui section. Keep colored setup as
592 # Honor color setting of ui section. Keep colored setup as
593 # long as not explicitly set to a falsy value - especially,
593 # long as not explicitly set to a falsy value - especially,
594 # when not set at all. This is to stay most compatible with
594 # when not set at all. This is to stay most compatible with
595 # previous (color only) behaviour.
595 # previous (color only) behaviour.
596 uicolor = stringutil.parsebool(self.ui.config('ui', 'color'))
596 uicolor = stringutil.parsebool(self.ui.config('ui', 'color'))
597 self.usecolor = uicolor is not False
597 self.usecolor = uicolor is not False
598
598
599 # the currently selected header, hunk, or hunk-line
599 # the currently selected header, hunk, or hunk-line
600 self.currentselecteditem = self.headerlist[0]
600 self.currentselecteditem = self.headerlist[0]
601
601
602 # updated when printing out patch-display -- the 'lines' here are the
602 # updated when printing out patch-display -- the 'lines' here are the
603 # line positions *in the pad*, not on the screen.
603 # line positions *in the pad*, not on the screen.
604 self.selecteditemstartline = 0
604 self.selecteditemstartline = 0
605 self.selecteditemendline = None
605 self.selecteditemendline = None
606
606
607 # define indentation levels
607 # define indentation levels
608 self.headerindentnumchars = 0
608 self.headerindentnumchars = 0
609 self.hunkindentnumchars = 3
609 self.hunkindentnumchars = 3
610 self.hunklineindentnumchars = 6
610 self.hunklineindentnumchars = 6
611
611
612 # the first line of the pad to print to the screen
612 # the first line of the pad to print to the screen
613 self.firstlineofpadtoprint = 0
613 self.firstlineofpadtoprint = 0
614
614
615 # keeps track of the number of lines in the pad
615 # keeps track of the number of lines in the pad
616 self.numpadlines = None
616 self.numpadlines = None
617
617
618 self.numstatuslines = 1
618 self.numstatuslines = 1
619
619
620 # keep a running count of the number of lines printed to the pad
620 # keep a running count of the number of lines printed to the pad
621 # (used for determining when the selected item begins/ends)
621 # (used for determining when the selected item begins/ends)
622 self.linesprintedtopadsofar = 0
622 self.linesprintedtopadsofar = 0
623
623
624 # the first line of the pad which is visible on the screen
624 # the first line of the pad which is visible on the screen
625 self.firstlineofpadtoprint = 0
625 self.firstlineofpadtoprint = 0
626
626
627 # stores optional text for a commit comment provided by the user
627 # stores optional text for a commit comment provided by the user
628 self.commenttext = ""
628 self.commenttext = ""
629
629
630 # if the last 'toggle all' command caused all changes to be applied
630 # if the last 'toggle all' command caused all changes to be applied
631 self.waslasttoggleallapplied = True
631 self.waslasttoggleallapplied = True
632
632
633 # affects some ui text
633 # affects some ui text
634 if operation not in _headermessages:
634 if operation not in _headermessages:
635 raise error.ProgrammingError('unexpected operation: %s' % operation)
635 raise error.ProgrammingError('unexpected operation: %s' % operation)
636 self.operation = operation
636 self.operation = operation
637
637
638 def uparrowevent(self):
638 def uparrowevent(self):
639 """
639 """
640 try to select the previous item to the current item that has the
640 try to select the previous item to the current item that has the
641 most-indented level. for example, if a hunk is selected, try to select
641 most-indented level. for example, if a hunk is selected, try to select
642 the last hunkline of the hunk prior to the selected hunk. or, if
642 the last hunkline of the hunk prior to the selected hunk. or, if
643 the first hunkline of a hunk is currently selected, then select the
643 the first hunkline of a hunk is currently selected, then select the
644 hunk itself.
644 hunk itself.
645 """
645 """
646 currentitem = self.currentselecteditem
646 currentitem = self.currentselecteditem
647
647
648 nextitem = currentitem.previtem()
648 nextitem = currentitem.previtem()
649
649
650 if nextitem is None:
650 if nextitem is None:
651 # if no parent item (i.e. currentitem is the first header), then
651 # if no parent item (i.e. currentitem is the first header), then
652 # no change...
652 # no change...
653 nextitem = currentitem
653 nextitem = currentitem
654
654
655 self.currentselecteditem = nextitem
655 self.currentselecteditem = nextitem
656
656
657 def uparrowshiftevent(self):
657 def uparrowshiftevent(self):
658 """
658 """
659 select (if possible) the previous item on the same level as the
659 select (if possible) the previous item on the same level as the
660 currently selected item. otherwise, select (if possible) the
660 currently selected item. otherwise, select (if possible) the
661 parent-item of the currently selected item.
661 parent-item of the currently selected item.
662 """
662 """
663 currentitem = self.currentselecteditem
663 currentitem = self.currentselecteditem
664 nextitem = currentitem.prevsibling()
664 nextitem = currentitem.prevsibling()
665 # if there's no previous sibling, try choosing the parent
665 # if there's no previous sibling, try choosing the parent
666 if nextitem is None:
666 if nextitem is None:
667 nextitem = currentitem.parentitem()
667 nextitem = currentitem.parentitem()
668 if nextitem is None:
668 if nextitem is None:
669 # if no parent item (i.e. currentitem is the first header), then
669 # if no parent item (i.e. currentitem is the first header), then
670 # no change...
670 # no change...
671 nextitem = currentitem
671 nextitem = currentitem
672
672
673 self.currentselecteditem = nextitem
673 self.currentselecteditem = nextitem
674 self.recenterdisplayedarea()
674 self.recenterdisplayedarea()
675
675
676 def downarrowevent(self):
676 def downarrowevent(self):
677 """
677 """
678 try to select the next item to the current item that has the
678 try to select the next item to the current item that has the
679 most-indented level. for example, if a hunk is selected, select
679 most-indented level. for example, if a hunk is selected, select
680 the first hunkline of the selected hunk. or, if the last hunkline of
680 the first hunkline of the selected hunk. or, if the last hunkline of
681 a hunk is currently selected, then select the next hunk, if one exists,
681 a hunk is currently selected, then select the next hunk, if one exists,
682 or if not, the next header if one exists.
682 or if not, the next header if one exists.
683 """
683 """
684 #self.startprintline += 1 #debug
684 #self.startprintline += 1 #debug
685 currentitem = self.currentselecteditem
685 currentitem = self.currentselecteditem
686
686
687 nextitem = currentitem.nextitem()
687 nextitem = currentitem.nextitem()
688 # if there's no next item, keep the selection as-is
688 # if there's no next item, keep the selection as-is
689 if nextitem is None:
689 if nextitem is None:
690 nextitem = currentitem
690 nextitem = currentitem
691
691
692 self.currentselecteditem = nextitem
692 self.currentselecteditem = nextitem
693
693
694 def downarrowshiftevent(self):
694 def downarrowshiftevent(self):
695 """
695 """
696 select (if possible) the next item on the same level as the currently
696 select (if possible) the next item on the same level as the currently
697 selected item. otherwise, select (if possible) the next item on the
697 selected item. otherwise, select (if possible) the next item on the
698 same level as the parent item of the currently selected item.
698 same level as the parent item of the currently selected item.
699 """
699 """
700 currentitem = self.currentselecteditem
700 currentitem = self.currentselecteditem
701 nextitem = currentitem.nextsibling()
701 nextitem = currentitem.nextsibling()
702 # if there's no next sibling, try choosing the parent's nextsibling
702 # if there's no next sibling, try choosing the parent's nextsibling
703 if nextitem is None:
703 if nextitem is None:
704 try:
704 try:
705 nextitem = currentitem.parentitem().nextsibling()
705 nextitem = currentitem.parentitem().nextsibling()
706 except AttributeError:
706 except AttributeError:
707 # parentitem returned None, so nextsibling() can't be called
707 # parentitem returned None, so nextsibling() can't be called
708 nextitem = None
708 nextitem = None
709 if nextitem is None:
709 if nextitem is None:
710 # if parent has no next sibling, then no change...
710 # if parent has no next sibling, then no change...
711 nextitem = currentitem
711 nextitem = currentitem
712
712
713 self.currentselecteditem = nextitem
713 self.currentselecteditem = nextitem
714 self.recenterdisplayedarea()
714 self.recenterdisplayedarea()
715
715
716 def rightarrowevent(self):
716 def rightarrowevent(self):
717 """
717 """
718 select (if possible) the first of this item's child-items.
718 select (if possible) the first of this item's child-items.
719 """
719 """
720 currentitem = self.currentselecteditem
720 currentitem = self.currentselecteditem
721 nextitem = currentitem.firstchild()
721 nextitem = currentitem.firstchild()
722
722
723 # turn off folding if we want to show a child-item
723 # turn off folding if we want to show a child-item
724 if currentitem.folded:
724 if currentitem.folded:
725 self.togglefolded(currentitem)
725 self.togglefolded(currentitem)
726
726
727 if nextitem is None:
727 if nextitem is None:
728 # if no next item on parent-level, then no change...
728 # if no next item on parent-level, then no change...
729 nextitem = currentitem
729 nextitem = currentitem
730
730
731 self.currentselecteditem = nextitem
731 self.currentselecteditem = nextitem
732
732
733 def leftarrowevent(self):
733 def leftarrowevent(self):
734 """
734 """
735 if the current item can be folded (i.e. it is an unfolded header or
735 if the current item can be folded (i.e. it is an unfolded header or
736 hunk), then fold it. otherwise try select (if possible) the parent
736 hunk), then fold it. otherwise try select (if possible) the parent
737 of this item.
737 of this item.
738 """
738 """
739 currentitem = self.currentselecteditem
739 currentitem = self.currentselecteditem
740
740
741 # try to fold the item
741 # try to fold the item
742 if not isinstance(currentitem, uihunkline):
742 if not isinstance(currentitem, uihunkline):
743 if not currentitem.folded:
743 if not currentitem.folded:
744 self.togglefolded(item=currentitem)
744 self.togglefolded(item=currentitem)
745 return
745 return
746
746
747 # if it can't be folded, try to select the parent item
747 # if it can't be folded, try to select the parent item
748 nextitem = currentitem.parentitem()
748 nextitem = currentitem.parentitem()
749
749
750 if nextitem is None:
750 if nextitem is None:
751 # if no item on parent-level, then no change...
751 # if no item on parent-level, then no change...
752 nextitem = currentitem
752 nextitem = currentitem
753 if not nextitem.folded:
753 if not nextitem.folded:
754 self.togglefolded(item=nextitem)
754 self.togglefolded(item=nextitem)
755
755
756 self.currentselecteditem = nextitem
756 self.currentselecteditem = nextitem
757
757
758 def leftarrowshiftevent(self):
758 def leftarrowshiftevent(self):
759 """
759 """
760 select the header of the current item (or fold current item if the
760 select the header of the current item (or fold current item if the
761 current item is already a header).
761 current item is already a header).
762 """
762 """
763 currentitem = self.currentselecteditem
763 currentitem = self.currentselecteditem
764
764
765 if isinstance(currentitem, uiheader):
765 if isinstance(currentitem, uiheader):
766 if not currentitem.folded:
766 if not currentitem.folded:
767 self.togglefolded(item=currentitem)
767 self.togglefolded(item=currentitem)
768 return
768 return
769
769
770 # select the parent item recursively until we're at a header
770 # select the parent item recursively until we're at a header
771 while True:
771 while True:
772 nextitem = currentitem.parentitem()
772 nextitem = currentitem.parentitem()
773 if nextitem is None:
773 if nextitem is None:
774 break
774 break
775 else:
775 else:
776 currentitem = nextitem
776 currentitem = nextitem
777
777
778 self.currentselecteditem = currentitem
778 self.currentselecteditem = currentitem
779
779
780 def updatescroll(self):
780 def updatescroll(self):
781 "scroll the screen to fully show the currently-selected"
781 "scroll the screen to fully show the currently-selected"
782 selstart = self.selecteditemstartline
782 selstart = self.selecteditemstartline
783 selend = self.selecteditemendline
783 selend = self.selecteditemendline
784
784
785 padstart = self.firstlineofpadtoprint
785 padstart = self.firstlineofpadtoprint
786 padend = padstart + self.yscreensize - self.numstatuslines - 1
786 padend = padstart + self.yscreensize - self.numstatuslines - 1
787 # 'buffered' pad start/end values which scroll with a certain
787 # 'buffered' pad start/end values which scroll with a certain
788 # top/bottom context margin
788 # top/bottom context margin
789 padstartbuffered = padstart + 3
789 padstartbuffered = padstart + 3
790 padendbuffered = padend - 3
790 padendbuffered = padend - 3
791
791
792 if selend > padendbuffered:
792 if selend > padendbuffered:
793 self.scrolllines(selend - padendbuffered)
793 self.scrolllines(selend - padendbuffered)
794 elif selstart < padstartbuffered:
794 elif selstart < padstartbuffered:
795 # negative values scroll in pgup direction
795 # negative values scroll in pgup direction
796 self.scrolllines(selstart - padstartbuffered)
796 self.scrolllines(selstart - padstartbuffered)
797
797
798 def scrolllines(self, numlines):
798 def scrolllines(self, numlines):
799 "scroll the screen up (down) by numlines when numlines >0 (<0)."
799 "scroll the screen up (down) by numlines when numlines >0 (<0)."
800 self.firstlineofpadtoprint += numlines
800 self.firstlineofpadtoprint += numlines
801 if self.firstlineofpadtoprint < 0:
801 if self.firstlineofpadtoprint < 0:
802 self.firstlineofpadtoprint = 0
802 self.firstlineofpadtoprint = 0
803 if self.firstlineofpadtoprint > self.numpadlines - 1:
803 if self.firstlineofpadtoprint > self.numpadlines - 1:
804 self.firstlineofpadtoprint = self.numpadlines - 1
804 self.firstlineofpadtoprint = self.numpadlines - 1
805
805
806 def toggleapply(self, item=None):
806 def toggleapply(self, item=None):
807 """
807 """
808 toggle the applied flag of the specified item. if no item is specified,
808 toggle the applied flag of the specified item. if no item is specified,
809 toggle the flag of the currently selected item.
809 toggle the flag of the currently selected item.
810 """
810 """
811 if item is None:
811 if item is None:
812 item = self.currentselecteditem
812 item = self.currentselecteditem
813
813
814 item.applied = not item.applied
814 item.applied = not item.applied
815
815
816 if isinstance(item, uiheader):
816 if isinstance(item, uiheader):
817 item.partial = False
817 item.partial = False
818 if item.applied:
818 if item.applied:
819 # apply all its hunks
819 # apply all its hunks
820 for hnk in item.hunks:
820 for hnk in item.hunks:
821 hnk.applied = True
821 hnk.applied = True
822 # apply all their hunklines
822 # apply all their hunklines
823 for hunkline in hnk.changedlines:
823 for hunkline in hnk.changedlines:
824 hunkline.applied = True
824 hunkline.applied = True
825 else:
825 else:
826 # un-apply all its hunks
826 # un-apply all its hunks
827 for hnk in item.hunks:
827 for hnk in item.hunks:
828 hnk.applied = False
828 hnk.applied = False
829 hnk.partial = False
829 hnk.partial = False
830 # un-apply all their hunklines
830 # un-apply all their hunklines
831 for hunkline in hnk.changedlines:
831 for hunkline in hnk.changedlines:
832 hunkline.applied = False
832 hunkline.applied = False
833 elif isinstance(item, uihunk):
833 elif isinstance(item, uihunk):
834 item.partial = False
834 item.partial = False
835 # apply all it's hunklines
835 # apply all it's hunklines
836 for hunkline in item.changedlines:
836 for hunkline in item.changedlines:
837 hunkline.applied = item.applied
837 hunkline.applied = item.applied
838
838
839 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
839 siblingappliedstatus = [hnk.applied for hnk in item.header.hunks]
840 allsiblingsapplied = not (False in siblingappliedstatus)
840 allsiblingsapplied = not (False in siblingappliedstatus)
841 nosiblingsapplied = not (True in siblingappliedstatus)
841 nosiblingsapplied = not (True in siblingappliedstatus)
842
842
843 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
843 siblingspartialstatus = [hnk.partial for hnk in item.header.hunks]
844 somesiblingspartial = (True in siblingspartialstatus)
844 somesiblingspartial = (True in siblingspartialstatus)
845
845
846 #cases where applied or partial should be removed from header
846 #cases where applied or partial should be removed from header
847
847
848 # if no 'sibling' hunks are applied (including this hunk)
848 # if no 'sibling' hunks are applied (including this hunk)
849 if nosiblingsapplied:
849 if nosiblingsapplied:
850 if not item.header.special():
850 if not item.header.special():
851 item.header.applied = False
851 item.header.applied = False
852 item.header.partial = False
852 item.header.partial = False
853 else: # some/all parent siblings are applied
853 else: # some/all parent siblings are applied
854 item.header.applied = True
854 item.header.applied = True
855 item.header.partial = (somesiblingspartial or
855 item.header.partial = (somesiblingspartial or
856 not allsiblingsapplied)
856 not allsiblingsapplied)
857
857
858 elif isinstance(item, uihunkline):
858 elif isinstance(item, uihunkline):
859 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
859 siblingappliedstatus = [ln.applied for ln in item.hunk.changedlines]
860 allsiblingsapplied = not (False in siblingappliedstatus)
860 allsiblingsapplied = not (False in siblingappliedstatus)
861 nosiblingsapplied = not (True in siblingappliedstatus)
861 nosiblingsapplied = not (True in siblingappliedstatus)
862
862
863 # if no 'sibling' lines are applied
863 # if no 'sibling' lines are applied
864 if nosiblingsapplied:
864 if nosiblingsapplied:
865 item.hunk.applied = False
865 item.hunk.applied = False
866 item.hunk.partial = False
866 item.hunk.partial = False
867 elif allsiblingsapplied:
867 elif allsiblingsapplied:
868 item.hunk.applied = True
868 item.hunk.applied = True
869 item.hunk.partial = False
869 item.hunk.partial = False
870 else: # some siblings applied
870 else: # some siblings applied
871 item.hunk.applied = True
871 item.hunk.applied = True
872 item.hunk.partial = True
872 item.hunk.partial = True
873
873
874 parentsiblingsapplied = [hnk.applied for hnk
874 parentsiblingsapplied = [hnk.applied for hnk
875 in item.hunk.header.hunks]
875 in item.hunk.header.hunks]
876 noparentsiblingsapplied = not (True in parentsiblingsapplied)
876 noparentsiblingsapplied = not (True in parentsiblingsapplied)
877 allparentsiblingsapplied = not (False in parentsiblingsapplied)
877 allparentsiblingsapplied = not (False in parentsiblingsapplied)
878
878
879 parentsiblingspartial = [hnk.partial for hnk
879 parentsiblingspartial = [hnk.partial for hnk
880 in item.hunk.header.hunks]
880 in item.hunk.header.hunks]
881 someparentsiblingspartial = (True in parentsiblingspartial)
881 someparentsiblingspartial = (True in parentsiblingspartial)
882
882
883 # if all parent hunks are not applied, un-apply header
883 # if all parent hunks are not applied, un-apply header
884 if noparentsiblingsapplied:
884 if noparentsiblingsapplied:
885 if not item.hunk.header.special():
885 if not item.hunk.header.special():
886 item.hunk.header.applied = False
886 item.hunk.header.applied = False
887 item.hunk.header.partial = False
887 item.hunk.header.partial = False
888 # set the applied and partial status of the header if needed
888 # set the applied and partial status of the header if needed
889 else: # some/all parent siblings are applied
889 else: # some/all parent siblings are applied
890 item.hunk.header.applied = True
890 item.hunk.header.applied = True
891 item.hunk.header.partial = (someparentsiblingspartial or
891 item.hunk.header.partial = (someparentsiblingspartial or
892 not allparentsiblingsapplied)
892 not allparentsiblingsapplied)
893
893
894 def toggleall(self):
894 def toggleall(self):
895 "toggle the applied flag of all items."
895 "toggle the applied flag of all items."
896 if self.waslasttoggleallapplied: # then unapply them this time
896 if self.waslasttoggleallapplied: # then unapply them this time
897 for item in self.headerlist:
897 for item in self.headerlist:
898 if item.applied:
898 if item.applied:
899 self.toggleapply(item)
899 self.toggleapply(item)
900 else:
900 else:
901 for item in self.headerlist:
901 for item in self.headerlist:
902 if not item.applied:
902 if not item.applied:
903 self.toggleapply(item)
903 self.toggleapply(item)
904 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
904 self.waslasttoggleallapplied = not self.waslasttoggleallapplied
905
905
906 def togglefolded(self, item=None, foldparent=False):
906 def togglefolded(self, item=None, foldparent=False):
907 "toggle folded flag of specified item (defaults to currently selected)"
907 "toggle folded flag of specified item (defaults to currently selected)"
908 if item is None:
908 if item is None:
909 item = self.currentselecteditem
909 item = self.currentselecteditem
910 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
910 if foldparent or (isinstance(item, uiheader) and item.neverunfolded):
911 if not isinstance(item, uiheader):
911 if not isinstance(item, uiheader):
912 # we need to select the parent item in this case
912 # we need to select the parent item in this case
913 self.currentselecteditem = item = item.parentitem()
913 self.currentselecteditem = item = item.parentitem()
914 elif item.neverunfolded:
914 elif item.neverunfolded:
915 item.neverunfolded = False
915 item.neverunfolded = False
916
916
917 # also fold any foldable children of the parent/current item
917 # also fold any foldable children of the parent/current item
918 if isinstance(item, uiheader): # the original or 'new' item
918 if isinstance(item, uiheader): # the original or 'new' item
919 for child in item.allchildren():
919 for child in item.allchildren():
920 child.folded = not item.folded
920 child.folded = not item.folded
921
921
922 if isinstance(item, (uiheader, uihunk)):
922 if isinstance(item, (uiheader, uihunk)):
923 item.folded = not item.folded
923 item.folded = not item.folded
924
924
925 def alignstring(self, instr, window):
925 def alignstring(self, instr, window):
926 """
926 """
927 add whitespace to the end of a string in order to make it fill
927 add whitespace to the end of a string in order to make it fill
928 the screen in the x direction. the current cursor position is
928 the screen in the x direction. the current cursor position is
929 taken into account when making this calculation. the string can span
929 taken into account when making this calculation. the string can span
930 multiple lines.
930 multiple lines.
931 """
931 """
932 y, xstart = window.getyx()
932 y, xstart = window.getyx()
933 width = self.xscreensize
933 width = self.xscreensize
934 # turn tabs into spaces
934 # turn tabs into spaces
935 instr = instr.expandtabs(4)
935 instr = instr.expandtabs(4)
936 strwidth = encoding.colwidth(instr)
936 strwidth = encoding.colwidth(instr)
937 numspaces = (width - ((strwidth + xstart) % width) - 1)
937 numspaces = (width - ((strwidth + xstart) % width) - 1)
938 return instr + " " * numspaces + "\n"
938 return instr + " " * numspaces + "\n"
939
939
940 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
940 def printstring(self, window, text, fgcolor=None, bgcolor=None, pair=None,
941 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
941 pairname=None, attrlist=None, towin=True, align=True, showwhtspc=False):
942 """
942 """
943 print the string, text, with the specified colors and attributes, to
943 print the string, text, with the specified colors and attributes, to
944 the specified curses window object.
944 the specified curses window object.
945
945
946 the foreground and background colors are of the form
946 the foreground and background colors are of the form
947 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
947 curses.color_xxxx, where xxxx is one of: [black, blue, cyan, green,
948 magenta, red, white, yellow]. if pairname is provided, a color
948 magenta, red, white, yellow]. if pairname is provided, a color
949 pair will be looked up in the self.colorpairnames dictionary.
949 pair will be looked up in the self.colorpairnames dictionary.
950
950
951 attrlist is a list containing text attributes in the form of
951 attrlist is a list containing text attributes in the form of
952 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
952 curses.a_xxxx, where xxxx can be: [bold, dim, normal, standout,
953 underline].
953 underline].
954
954
955 if align == True, whitespace is added to the printed string such that
955 if align == True, whitespace is added to the printed string such that
956 the string stretches to the right border of the window.
956 the string stretches to the right border of the window.
957
957
958 if showwhtspc == True, trailing whitespace of a string is highlighted.
958 if showwhtspc == True, trailing whitespace of a string is highlighted.
959 """
959 """
960 # preprocess the text, converting tabs to spaces
960 # preprocess the text, converting tabs to spaces
961 text = text.expandtabs(4)
961 text = text.expandtabs(4)
962 # strip \n, and convert control characters to ^[char] representation
962 # strip \n, and convert control characters to ^[char] representation
963 text = re.sub(br'[\x00-\x08\x0a-\x1f]',
963 text = re.sub(br'[\x00-\x08\x0a-\x1f]',
964 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
964 lambda m:'^' + chr(ord(m.group()) + 64), text.strip('\n'))
965
965
966 if pair is not None:
966 if pair is not None:
967 colorpair = pair
967 colorpair = pair
968 elif pairname is not None:
968 elif pairname is not None:
969 colorpair = self.colorpairnames[pairname]
969 colorpair = self.colorpairnames[pairname]
970 else:
970 else:
971 if fgcolor is None:
971 if fgcolor is None:
972 fgcolor = -1
972 fgcolor = -1
973 if bgcolor is None:
973 if bgcolor is None:
974 bgcolor = -1
974 bgcolor = -1
975 if (fgcolor, bgcolor) in self.colorpairs:
975 if (fgcolor, bgcolor) in self.colorpairs:
976 colorpair = self.colorpairs[(fgcolor, bgcolor)]
976 colorpair = self.colorpairs[(fgcolor, bgcolor)]
977 else:
977 else:
978 colorpair = self.getcolorpair(fgcolor, bgcolor)
978 colorpair = self.getcolorpair(fgcolor, bgcolor)
979 # add attributes if possible
979 # add attributes if possible
980 if attrlist is None:
980 if attrlist is None:
981 attrlist = []
981 attrlist = []
982 if colorpair < 256:
982 if colorpair < 256:
983 # then it is safe to apply all attributes
983 # then it is safe to apply all attributes
984 for textattr in attrlist:
984 for textattr in attrlist:
985 colorpair |= textattr
985 colorpair |= textattr
986 else:
986 else:
987 # just apply a select few (safe?) attributes
987 # just apply a select few (safe?) attributes
988 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
988 for textattr in (curses.A_UNDERLINE, curses.A_BOLD):
989 if textattr in attrlist:
989 if textattr in attrlist:
990 colorpair |= textattr
990 colorpair |= textattr
991
991
992 y, xstart = self.chunkpad.getyx()
992 y, xstart = self.chunkpad.getyx()
993 t = "" # variable for counting lines printed
993 t = "" # variable for counting lines printed
994 # if requested, show trailing whitespace
994 # if requested, show trailing whitespace
995 if showwhtspc:
995 if showwhtspc:
996 origlen = len(text)
996 origlen = len(text)
997 text = text.rstrip(' \n') # tabs have already been expanded
997 text = text.rstrip(' \n') # tabs have already been expanded
998 strippedlen = len(text)
998 strippedlen = len(text)
999 numtrailingspaces = origlen - strippedlen
999 numtrailingspaces = origlen - strippedlen
1000
1000
1001 if towin:
1001 if towin:
1002 window.addstr(text, colorpair)
1002 window.addstr(text, colorpair)
1003 t += text
1003 t += text
1004
1004
1005 if showwhtspc:
1005 if showwhtspc:
1006 wscolorpair = colorpair | curses.A_REVERSE
1006 wscolorpair = colorpair | curses.A_REVERSE
1007 if towin:
1007 if towin:
1008 for i in range(numtrailingspaces):
1008 for i in range(numtrailingspaces):
1009 window.addch(curses.ACS_CKBOARD, wscolorpair)
1009 window.addch(curses.ACS_CKBOARD, wscolorpair)
1010 t += " " * numtrailingspaces
1010 t += " " * numtrailingspaces
1011
1011
1012 if align:
1012 if align:
1013 if towin:
1013 if towin:
1014 extrawhitespace = self.alignstring("", window)
1014 extrawhitespace = self.alignstring("", window)
1015 window.addstr(extrawhitespace, colorpair)
1015 window.addstr(extrawhitespace, colorpair)
1016 else:
1016 else:
1017 # need to use t, since the x position hasn't incremented
1017 # need to use t, since the x position hasn't incremented
1018 extrawhitespace = self.alignstring(t, window)
1018 extrawhitespace = self.alignstring(t, window)
1019 t += extrawhitespace
1019 t += extrawhitespace
1020
1020
1021 # is reset to 0 at the beginning of printitem()
1021 # is reset to 0 at the beginning of printitem()
1022
1022
1023 linesprinted = (xstart + len(t)) / self.xscreensize
1023 linesprinted = (xstart + len(t)) / self.xscreensize
1024 self.linesprintedtopadsofar += linesprinted
1024 self.linesprintedtopadsofar += linesprinted
1025 return t
1025 return t
1026
1026
1027 def _getstatuslinesegments(self):
1027 def _getstatuslinesegments(self):
1028 """-> [str]. return segments"""
1028 """-> [str]. return segments"""
1029 selected = self.currentselecteditem.applied
1029 selected = self.currentselecteditem.applied
1030 spaceselect = _('space: select')
1030 spaceselect = _('space: select')
1031 spacedeselect = _('space: deselect')
1031 spacedeselect = _('space: deselect')
1032 # Format the selected label into a place as long as the longer of the
1032 # Format the selected label into a place as long as the longer of the
1033 # two possible labels. This may vary by language.
1033 # two possible labels. This may vary by language.
1034 spacelen = max(len(spaceselect), len(spacedeselect))
1034 spacelen = max(len(spaceselect), len(spacedeselect))
1035 selectedlabel = '%-*s' % (spacelen,
1035 selectedlabel = '%-*s' % (spacelen,
1036 spacedeselect if selected else spaceselect)
1036 spacedeselect if selected else spaceselect)
1037 segments = [
1037 segments = [
1038 _headermessages[self.operation],
1038 _headermessages[self.operation],
1039 '-',
1039 '-',
1040 _('[x]=selected **=collapsed'),
1040 _('[x]=selected **=collapsed'),
1041 _('c: confirm'),
1041 _('c: confirm'),
1042 _('q: abort'),
1042 _('q: abort'),
1043 _('arrow keys: move/expand/collapse'),
1043 _('arrow keys: move/expand/collapse'),
1044 selectedlabel,
1044 selectedlabel,
1045 _('?: help'),
1045 _('?: help'),
1046 ]
1046 ]
1047 return segments
1047 return segments
1048
1048
1049 def _getstatuslines(self):
1049 def _getstatuslines(self):
1050 """() -> [str]. return short help used in the top status window"""
1050 """() -> [str]. return short help used in the top status window"""
1051 if self.errorstr is not None:
1051 if self.errorstr is not None:
1052 lines = [self.errorstr, _('Press any key to continue')]
1052 lines = [self.errorstr, _('Press any key to continue')]
1053 else:
1053 else:
1054 # wrap segments to lines
1054 # wrap segments to lines
1055 segments = self._getstatuslinesegments()
1055 segments = self._getstatuslinesegments()
1056 width = self.xscreensize
1056 width = self.xscreensize
1057 lines = []
1057 lines = []
1058 lastwidth = width
1058 lastwidth = width
1059 for s in segments:
1059 for s in segments:
1060 w = encoding.colwidth(s)
1060 w = encoding.colwidth(s)
1061 sep = ' ' * (1 + (s and s[0] not in '-['))
1061 sep = ' ' * (1 + (s and s[0] not in '-['))
1062 if lastwidth + w + len(sep) >= width:
1062 if lastwidth + w + len(sep) >= width:
1063 lines.append(s)
1063 lines.append(s)
1064 lastwidth = w
1064 lastwidth = w
1065 else:
1065 else:
1066 lines[-1] += sep + s
1066 lines[-1] += sep + s
1067 lastwidth += w + len(sep)
1067 lastwidth += w + len(sep)
1068 if len(lines) != self.numstatuslines:
1068 if len(lines) != self.numstatuslines:
1069 self.numstatuslines = len(lines)
1069 self.numstatuslines = len(lines)
1070 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1070 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1071 return [stringutil.ellipsis(l, self.xscreensize - 1) for l in lines]
1071 return [stringutil.ellipsis(l, self.xscreensize - 1) for l in lines]
1072
1072
1073 def updatescreen(self):
1073 def updatescreen(self):
1074 self.statuswin.erase()
1074 self.statuswin.erase()
1075 self.chunkpad.erase()
1075 self.chunkpad.erase()
1076
1076
1077 printstring = self.printstring
1077 printstring = self.printstring
1078
1078
1079 # print out the status lines at the top
1079 # print out the status lines at the top
1080 try:
1080 try:
1081 for line in self._getstatuslines():
1081 for line in self._getstatuslines():
1082 printstring(self.statuswin, line, pairname="legend")
1082 printstring(self.statuswin, line, pairname="legend")
1083 self.statuswin.refresh()
1083 self.statuswin.refresh()
1084 except curses.error:
1084 except curses.error:
1085 pass
1085 pass
1086 if self.errorstr is not None:
1086 if self.errorstr is not None:
1087 return
1087 return
1088
1088
1089 # print out the patch in the remaining part of the window
1089 # print out the patch in the remaining part of the window
1090 try:
1090 try:
1091 self.printitem()
1091 self.printitem()
1092 self.updatescroll()
1092 self.updatescroll()
1093 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
1093 self.chunkpad.refresh(self.firstlineofpadtoprint, 0,
1094 self.numstatuslines, 0,
1094 self.numstatuslines, 0,
1095 self.yscreensize - self.numstatuslines,
1095 self.yscreensize - self.numstatuslines,
1096 self.xscreensize)
1096 self.xscreensize)
1097 except curses.error:
1097 except curses.error:
1098 pass
1098 pass
1099
1099
1100 def getstatusprefixstring(self, item):
1100 def getstatusprefixstring(self, item):
1101 """
1101 """
1102 create a string to prefix a line with which indicates whether 'item'
1102 create a string to prefix a line with which indicates whether 'item'
1103 is applied and/or folded.
1103 is applied and/or folded.
1104 """
1104 """
1105
1105
1106 # create checkbox string
1106 # create checkbox string
1107 if item.applied:
1107 if item.applied:
1108 if not isinstance(item, uihunkline) and item.partial:
1108 if not isinstance(item, uihunkline) and item.partial:
1109 checkbox = "[~]"
1109 checkbox = "[~]"
1110 else:
1110 else:
1111 checkbox = "[x]"
1111 checkbox = "[x]"
1112 else:
1112 else:
1113 checkbox = "[ ]"
1113 checkbox = "[ ]"
1114
1114
1115 try:
1115 try:
1116 if item.folded:
1116 if item.folded:
1117 checkbox += "**"
1117 checkbox += "**"
1118 if isinstance(item, uiheader):
1118 if isinstance(item, uiheader):
1119 # one of "m", "a", or "d" (modified, added, deleted)
1119 # one of "m", "a", or "d" (modified, added, deleted)
1120 filestatus = item.changetype
1120 filestatus = item.changetype
1121
1121
1122 checkbox += filestatus + " "
1122 checkbox += filestatus + " "
1123 else:
1123 else:
1124 checkbox += " "
1124 checkbox += " "
1125 if isinstance(item, uiheader):
1125 if isinstance(item, uiheader):
1126 # add two more spaces for headers
1126 # add two more spaces for headers
1127 checkbox += " "
1127 checkbox += " "
1128 except AttributeError: # not foldable
1128 except AttributeError: # not foldable
1129 checkbox += " "
1129 checkbox += " "
1130
1130
1131 return checkbox
1131 return checkbox
1132
1132
1133 def printheader(self, header, selected=False, towin=True,
1133 def printheader(self, header, selected=False, towin=True,
1134 ignorefolding=False):
1134 ignorefolding=False):
1135 """
1135 """
1136 print the header to the pad. if countlines is True, don't print
1136 print the header to the pad. if countlines is True, don't print
1137 anything, but just count the number of lines which would be printed.
1137 anything, but just count the number of lines which would be printed.
1138 """
1138 """
1139
1139
1140 outstr = ""
1140 outstr = ""
1141 text = header.prettystr()
1141 text = header.prettystr()
1142 chunkindex = self.chunklist.index(header)
1142 chunkindex = self.chunklist.index(header)
1143
1143
1144 if chunkindex != 0 and not header.folded:
1144 if chunkindex != 0 and not header.folded:
1145 # add separating line before headers
1145 # add separating line before headers
1146 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1146 outstr += self.printstring(self.chunkpad, '_' * self.xscreensize,
1147 towin=towin, align=False)
1147 towin=towin, align=False)
1148 # select color-pair based on if the header is selected
1148 # select color-pair based on if the header is selected
1149 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1149 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1150 attrlist=[curses.A_BOLD])
1150 attrlist=[curses.A_BOLD])
1151
1151
1152 # print out each line of the chunk, expanding it to screen width
1152 # print out each line of the chunk, expanding it to screen width
1153
1153
1154 # number of characters to indent lines on this level by
1154 # number of characters to indent lines on this level by
1155 indentnumchars = 0
1155 indentnumchars = 0
1156 checkbox = self.getstatusprefixstring(header)
1156 checkbox = self.getstatusprefixstring(header)
1157 if not header.folded or ignorefolding:
1157 if not header.folded or ignorefolding:
1158 textlist = text.split("\n")
1158 textlist = text.split("\n")
1159 linestr = checkbox + textlist[0]
1159 linestr = checkbox + textlist[0]
1160 else:
1160 else:
1161 linestr = checkbox + header.filename()
1161 linestr = checkbox + header.filename()
1162 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1162 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1163 towin=towin)
1163 towin=towin)
1164 if not header.folded or ignorefolding:
1164 if not header.folded or ignorefolding:
1165 if len(textlist) > 1:
1165 if len(textlist) > 1:
1166 for line in textlist[1:]:
1166 for line in textlist[1:]:
1167 linestr = " "*(indentnumchars + len(checkbox)) + line
1167 linestr = " "*(indentnumchars + len(checkbox)) + line
1168 outstr += self.printstring(self.chunkpad, linestr,
1168 outstr += self.printstring(self.chunkpad, linestr,
1169 pair=colorpair, towin=towin)
1169 pair=colorpair, towin=towin)
1170
1170
1171 return outstr
1171 return outstr
1172
1172
1173 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1173 def printhunklinesbefore(self, hunk, selected=False, towin=True,
1174 ignorefolding=False):
1174 ignorefolding=False):
1175 "includes start/end line indicator"
1175 "includes start/end line indicator"
1176 outstr = ""
1176 outstr = ""
1177 # where hunk is in list of siblings
1177 # where hunk is in list of siblings
1178 hunkindex = hunk.header.hunks.index(hunk)
1178 hunkindex = hunk.header.hunks.index(hunk)
1179
1179
1180 if hunkindex != 0:
1180 if hunkindex != 0:
1181 # add separating line before headers
1181 # add separating line before headers
1182 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1182 outstr += self.printstring(self.chunkpad, ' '*self.xscreensize,
1183 towin=towin, align=False)
1183 towin=towin, align=False)
1184
1184
1185 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1185 colorpair = self.getcolorpair(name=selected and "selected" or "normal",
1186 attrlist=[curses.A_BOLD])
1186 attrlist=[curses.A_BOLD])
1187
1187
1188 # print out from-to line with checkbox
1188 # print out from-to line with checkbox
1189 checkbox = self.getstatusprefixstring(hunk)
1189 checkbox = self.getstatusprefixstring(hunk)
1190
1190
1191 lineprefix = " "*self.hunkindentnumchars + checkbox
1191 lineprefix = " "*self.hunkindentnumchars + checkbox
1192 frtoline = " " + hunk.getfromtoline().strip("\n")
1192 frtoline = " " + hunk.getfromtoline().strip("\n")
1193
1193
1194 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1194 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1195 align=False) # add uncolored checkbox/indent
1195 align=False) # add uncolored checkbox/indent
1196 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1196 outstr += self.printstring(self.chunkpad, frtoline, pair=colorpair,
1197 towin=towin)
1197 towin=towin)
1198
1198
1199 if hunk.folded and not ignorefolding:
1199 if hunk.folded and not ignorefolding:
1200 # skip remainder of output
1200 # skip remainder of output
1201 return outstr
1201 return outstr
1202
1202
1203 # print out lines of the chunk preceeding changed-lines
1203 # print out lines of the chunk preceeding changed-lines
1204 for line in hunk.before:
1204 for line in hunk.before:
1205 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1205 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1206 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1206 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1207
1207
1208 return outstr
1208 return outstr
1209
1209
1210 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1210 def printhunklinesafter(self, hunk, towin=True, ignorefolding=False):
1211 outstr = ""
1211 outstr = ""
1212 if hunk.folded and not ignorefolding:
1212 if hunk.folded and not ignorefolding:
1213 return outstr
1213 return outstr
1214
1214
1215 # a bit superfluous, but to avoid hard-coding indent amount
1215 # a bit superfluous, but to avoid hard-coding indent amount
1216 checkbox = self.getstatusprefixstring(hunk)
1216 checkbox = self.getstatusprefixstring(hunk)
1217 for line in hunk.after:
1217 for line in hunk.after:
1218 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1218 linestr = " "*(self.hunklineindentnumchars + len(checkbox)) + line
1219 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1219 outstr += self.printstring(self.chunkpad, linestr, towin=towin)
1220
1220
1221 return outstr
1221 return outstr
1222
1222
1223 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1223 def printhunkchangedline(self, hunkline, selected=False, towin=True):
1224 outstr = ""
1224 outstr = ""
1225 checkbox = self.getstatusprefixstring(hunkline)
1225 checkbox = self.getstatusprefixstring(hunkline)
1226
1226
1227 linestr = hunkline.prettystr().strip("\n")
1227 linestr = hunkline.prettystr().strip("\n")
1228
1228
1229 # select color-pair based on whether line is an addition/removal
1229 # select color-pair based on whether line is an addition/removal
1230 if selected:
1230 if selected:
1231 colorpair = self.getcolorpair(name="selected")
1231 colorpair = self.getcolorpair(name="selected")
1232 elif linestr.startswith("+"):
1232 elif linestr.startswith("+"):
1233 colorpair = self.getcolorpair(name="addition")
1233 colorpair = self.getcolorpair(name="addition")
1234 elif linestr.startswith("-"):
1234 elif linestr.startswith("-"):
1235 colorpair = self.getcolorpair(name="deletion")
1235 colorpair = self.getcolorpair(name="deletion")
1236 elif linestr.startswith("\\"):
1236 elif linestr.startswith("\\"):
1237 colorpair = self.getcolorpair(name="normal")
1237 colorpair = self.getcolorpair(name="normal")
1238
1238
1239 lineprefix = " "*self.hunklineindentnumchars + checkbox
1239 lineprefix = " "*self.hunklineindentnumchars + checkbox
1240 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1240 outstr += self.printstring(self.chunkpad, lineprefix, towin=towin,
1241 align=False) # add uncolored checkbox/indent
1241 align=False) # add uncolored checkbox/indent
1242 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1242 outstr += self.printstring(self.chunkpad, linestr, pair=colorpair,
1243 towin=towin, showwhtspc=True)
1243 towin=towin, showwhtspc=True)
1244 return outstr
1244 return outstr
1245
1245
1246 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1246 def printitem(self, item=None, ignorefolding=False, recursechildren=True,
1247 towin=True):
1247 towin=True):
1248 """
1248 """
1249 use __printitem() to print the the specified item.applied.
1249 use __printitem() to print the the specified item.applied.
1250 if item is not specified, then print the entire patch.
1250 if item is not specified, then print the entire patch.
1251 (hiding folded elements, etc. -- see __printitem() docstring)
1251 (hiding folded elements, etc. -- see __printitem() docstring)
1252 """
1252 """
1253
1253
1254 if item is None:
1254 if item is None:
1255 item = self.headerlist
1255 item = self.headerlist
1256 if recursechildren:
1256 if recursechildren:
1257 self.linesprintedtopadsofar = 0
1257 self.linesprintedtopadsofar = 0
1258
1258
1259 outstr = []
1259 outstr = []
1260 self.__printitem(item, ignorefolding, recursechildren, outstr,
1260 self.__printitem(item, ignorefolding, recursechildren, outstr,
1261 towin=towin)
1261 towin=towin)
1262 return ''.join(outstr)
1262 return ''.join(outstr)
1263
1263
1264 def outofdisplayedarea(self):
1264 def outofdisplayedarea(self):
1265 y, _ = self.chunkpad.getyx() # cursor location
1265 y, _ = self.chunkpad.getyx() # cursor location
1266 # * 2 here works but an optimization would be the max number of
1266 # * 2 here works but an optimization would be the max number of
1267 # consecutive non selectable lines
1267 # consecutive non selectable lines
1268 # i.e the max number of context line for any hunk in the patch
1268 # i.e the max number of context line for any hunk in the patch
1269 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1269 miny = min(0, self.firstlineofpadtoprint - self.yscreensize)
1270 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1270 maxy = self.firstlineofpadtoprint + self.yscreensize * 2
1271 return y < miny or y > maxy
1271 return y < miny or y > maxy
1272
1272
1273 def handleselection(self, item, recursechildren):
1273 def handleselection(self, item, recursechildren):
1274 selected = (item is self.currentselecteditem)
1274 selected = (item is self.currentselecteditem)
1275 if selected and recursechildren:
1275 if selected and recursechildren:
1276 # assumes line numbering starting from line 0
1276 # assumes line numbering starting from line 0
1277 self.selecteditemstartline = self.linesprintedtopadsofar
1277 self.selecteditemstartline = self.linesprintedtopadsofar
1278 selecteditemlines = self.getnumlinesdisplayed(item,
1278 selecteditemlines = self.getnumlinesdisplayed(item,
1279 recursechildren=False)
1279 recursechildren=False)
1280 self.selecteditemendline = (self.selecteditemstartline +
1280 self.selecteditemendline = (self.selecteditemstartline +
1281 selecteditemlines - 1)
1281 selecteditemlines - 1)
1282 return selected
1282 return selected
1283
1283
1284 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1284 def __printitem(self, item, ignorefolding, recursechildren, outstr,
1285 towin=True):
1285 towin=True):
1286 """
1286 """
1287 recursive method for printing out patch/header/hunk/hunk-line data to
1287 recursive method for printing out patch/header/hunk/hunk-line data to
1288 screen. also returns a string with all of the content of the displayed
1288 screen. also returns a string with all of the content of the displayed
1289 patch (not including coloring, etc.).
1289 patch (not including coloring, etc.).
1290
1290
1291 if ignorefolding is True, then folded items are printed out.
1291 if ignorefolding is True, then folded items are printed out.
1292
1292
1293 if recursechildren is False, then only print the item without its
1293 if recursechildren is False, then only print the item without its
1294 child items.
1294 child items.
1295 """
1295 """
1296
1296
1297 if towin and self.outofdisplayedarea():
1297 if towin and self.outofdisplayedarea():
1298 return
1298 return
1299
1299
1300 selected = self.handleselection(item, recursechildren)
1300 selected = self.handleselection(item, recursechildren)
1301
1301
1302 # patch object is a list of headers
1302 # patch object is a list of headers
1303 if isinstance(item, patch):
1303 if isinstance(item, patch):
1304 if recursechildren:
1304 if recursechildren:
1305 for hdr in item:
1305 for hdr in item:
1306 self.__printitem(hdr, ignorefolding,
1306 self.__printitem(hdr, ignorefolding,
1307 recursechildren, outstr, towin)
1307 recursechildren, outstr, towin)
1308 # todo: eliminate all isinstance() calls
1308 # todo: eliminate all isinstance() calls
1309 if isinstance(item, uiheader):
1309 if isinstance(item, uiheader):
1310 outstr.append(self.printheader(item, selected, towin=towin,
1310 outstr.append(self.printheader(item, selected, towin=towin,
1311 ignorefolding=ignorefolding))
1311 ignorefolding=ignorefolding))
1312 if recursechildren:
1312 if recursechildren:
1313 for hnk in item.hunks:
1313 for hnk in item.hunks:
1314 self.__printitem(hnk, ignorefolding,
1314 self.__printitem(hnk, ignorefolding,
1315 recursechildren, outstr, towin)
1315 recursechildren, outstr, towin)
1316 elif (isinstance(item, uihunk) and
1316 elif (isinstance(item, uihunk) and
1317 ((not item.header.folded) or ignorefolding)):
1317 ((not item.header.folded) or ignorefolding)):
1318 # print the hunk data which comes before the changed-lines
1318 # print the hunk data which comes before the changed-lines
1319 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1319 outstr.append(self.printhunklinesbefore(item, selected, towin=towin,
1320 ignorefolding=ignorefolding))
1320 ignorefolding=ignorefolding))
1321 if recursechildren:
1321 if recursechildren:
1322 for l in item.changedlines:
1322 for l in item.changedlines:
1323 self.__printitem(l, ignorefolding,
1323 self.__printitem(l, ignorefolding,
1324 recursechildren, outstr, towin)
1324 recursechildren, outstr, towin)
1325 outstr.append(self.printhunklinesafter(item, towin=towin,
1325 outstr.append(self.printhunklinesafter(item, towin=towin,
1326 ignorefolding=ignorefolding))
1326 ignorefolding=ignorefolding))
1327 elif (isinstance(item, uihunkline) and
1327 elif (isinstance(item, uihunkline) and
1328 ((not item.hunk.folded) or ignorefolding)):
1328 ((not item.hunk.folded) or ignorefolding)):
1329 outstr.append(self.printhunkchangedline(item, selected,
1329 outstr.append(self.printhunkchangedline(item, selected,
1330 towin=towin))
1330 towin=towin))
1331
1331
1332 return outstr
1332 return outstr
1333
1333
1334 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1334 def getnumlinesdisplayed(self, item=None, ignorefolding=False,
1335 recursechildren=True):
1335 recursechildren=True):
1336 """
1336 """
1337 return the number of lines which would be displayed if the item were
1337 return the number of lines which would be displayed if the item were
1338 to be printed to the display. the item will not be printed to the
1338 to be printed to the display. the item will not be printed to the
1339 display (pad).
1339 display (pad).
1340 if no item is given, assume the entire patch.
1340 if no item is given, assume the entire patch.
1341 if ignorefolding is True, folded items will be unfolded when counting
1341 if ignorefolding is True, folded items will be unfolded when counting
1342 the number of lines.
1342 the number of lines.
1343 """
1343 """
1344
1344
1345 # temporarily disable printing to windows by printstring
1345 # temporarily disable printing to windows by printstring
1346 patchdisplaystring = self.printitem(item, ignorefolding,
1346 patchdisplaystring = self.printitem(item, ignorefolding,
1347 recursechildren, towin=False)
1347 recursechildren, towin=False)
1348 numlines = len(patchdisplaystring) // self.xscreensize
1348 numlines = len(patchdisplaystring) // self.xscreensize
1349 return numlines
1349 return numlines
1350
1350
1351 def sigwinchhandler(self, n, frame):
1351 def sigwinchhandler(self, n, frame):
1352 "handle window resizing"
1352 "handle window resizing"
1353 try:
1353 try:
1354 curses.endwin()
1354 curses.endwin()
1355 self.xscreensize, self.yscreensize = scmutil.termsize(self.ui)
1355 self.xscreensize, self.yscreensize = scmutil.termsize(self.ui)
1356 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1356 self.statuswin.resize(self.numstatuslines, self.xscreensize)
1357 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1357 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1358 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1358 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1359 except curses.error:
1359 except curses.error:
1360 pass
1360 pass
1361
1361
1362 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1362 def getcolorpair(self, fgcolor=None, bgcolor=None, name=None,
1363 attrlist=None):
1363 attrlist=None):
1364 """
1364 """
1365 get a curses color pair, adding it to self.colorpairs if it is not
1365 get a curses color pair, adding it to self.colorpairs if it is not
1366 already defined. an optional string, name, can be passed as a shortcut
1366 already defined. an optional string, name, can be passed as a shortcut
1367 for referring to the color-pair. by default, if no arguments are
1367 for referring to the color-pair. by default, if no arguments are
1368 specified, the white foreground / black background color-pair is
1368 specified, the white foreground / black background color-pair is
1369 returned.
1369 returned.
1370
1370
1371 it is expected that this function will be used exclusively for
1371 it is expected that this function will be used exclusively for
1372 initializing color pairs, and not curses.init_pair().
1372 initializing color pairs, and not curses.init_pair().
1373
1373
1374 attrlist is used to 'flavor' the returned color-pair. this information
1374 attrlist is used to 'flavor' the returned color-pair. this information
1375 is not stored in self.colorpairs. it contains attribute values like
1375 is not stored in self.colorpairs. it contains attribute values like
1376 curses.A_BOLD.
1376 curses.A_BOLD.
1377 """
1377 """
1378
1378
1379 if (name is not None) and name in self.colorpairnames:
1379 if (name is not None) and name in self.colorpairnames:
1380 # then get the associated color pair and return it
1380 # then get the associated color pair and return it
1381 colorpair = self.colorpairnames[name]
1381 colorpair = self.colorpairnames[name]
1382 else:
1382 else:
1383 if fgcolor is None:
1383 if fgcolor is None:
1384 fgcolor = -1
1384 fgcolor = -1
1385 if bgcolor is None:
1385 if bgcolor is None:
1386 bgcolor = -1
1386 bgcolor = -1
1387 if (fgcolor, bgcolor) in self.colorpairs:
1387 if (fgcolor, bgcolor) in self.colorpairs:
1388 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1388 colorpair = self.colorpairs[(fgcolor, bgcolor)]
1389 else:
1389 else:
1390 pairindex = len(self.colorpairs) + 1
1390 pairindex = len(self.colorpairs) + 1
1391 if self.usecolor:
1391 if self.usecolor:
1392 curses.init_pair(pairindex, fgcolor, bgcolor)
1392 curses.init_pair(pairindex, fgcolor, bgcolor)
1393 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1393 colorpair = self.colorpairs[(fgcolor, bgcolor)] = (
1394 curses.color_pair(pairindex))
1394 curses.color_pair(pairindex))
1395 if name is not None:
1395 if name is not None:
1396 self.colorpairnames[name] = curses.color_pair(pairindex)
1396 self.colorpairnames[name] = curses.color_pair(pairindex)
1397 else:
1397 else:
1398 cval = 0
1398 cval = 0
1399 if name is not None:
1399 if name is not None:
1400 if name == 'selected':
1400 if name == 'selected':
1401 cval = curses.A_REVERSE
1401 cval = curses.A_REVERSE
1402 self.colorpairnames[name] = cval
1402 self.colorpairnames[name] = cval
1403 colorpair = self.colorpairs[(fgcolor, bgcolor)] = cval
1403 colorpair = self.colorpairs[(fgcolor, bgcolor)] = cval
1404
1404
1405 # add attributes if possible
1405 # add attributes if possible
1406 if attrlist is None:
1406 if attrlist is None:
1407 attrlist = []
1407 attrlist = []
1408 if colorpair < 256:
1408 if colorpair < 256:
1409 # then it is safe to apply all attributes
1409 # then it is safe to apply all attributes
1410 for textattr in attrlist:
1410 for textattr in attrlist:
1411 colorpair |= textattr
1411 colorpair |= textattr
1412 else:
1412 else:
1413 # just apply a select few (safe?) attributes
1413 # just apply a select few (safe?) attributes
1414 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1414 for textattrib in (curses.A_UNDERLINE, curses.A_BOLD):
1415 if textattrib in attrlist:
1415 if textattrib in attrlist:
1416 colorpair |= textattrib
1416 colorpair |= textattrib
1417 return colorpair
1417 return colorpair
1418
1418
1419 def initcolorpair(self, *args, **kwargs):
1419 def initcolorpair(self, *args, **kwargs):
1420 "same as getcolorpair."
1420 "same as getcolorpair."
1421 self.getcolorpair(*args, **kwargs)
1421 self.getcolorpair(*args, **kwargs)
1422
1422
1423 def helpwindow(self):
1423 def helpwindow(self):
1424 "print a help window to the screen. exit after any keypress."
1424 "print a help window to the screen. exit after any keypress."
1425 helptext = _(
1425 helptext = _(
1426 """ [press any key to return to the patch-display]
1426 """ [press any key to return to the patch-display]
1427
1427
1428 crecord allows you to interactively choose among the changes you have made,
1428 crecord allows you to interactively choose among the changes you have made,
1429 and confirm only those changes you select for further processing by the command
1429 and confirm only those changes you select for further processing by the command
1430 you are running (commit/shelve/revert), after confirming the selected
1430 you are running (commit/shelve/revert), after confirming the selected
1431 changes, the unselected changes are still present in your working copy, so you
1431 changes, the unselected changes are still present in your working copy, so you
1432 can use crecord multiple times to split large changes into smaller changesets.
1432 can use crecord multiple times to split large changes into smaller changesets.
1433 the following are valid keystrokes:
1433 the following are valid keystrokes:
1434
1434
1435 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1435 [space] : (un-)select item ([~]/[x] = partly/fully applied)
1436 A : (un-)select all items
1436 A : (un-)select all items
1437 up/down-arrow [k/j] : go to previous/next unfolded item
1437 up/down-arrow [k/j] : go to previous/next unfolded item
1438 pgup/pgdn [K/J] : go to previous/next item of same type
1438 pgup/pgdn [K/J] : go to previous/next item of same type
1439 right/left-arrow [l/h] : go to child item / parent item
1439 right/left-arrow [l/h] : go to child item / parent item
1440 shift-left-arrow [H] : go to parent header / fold selected header
1440 shift-left-arrow [H] : go to parent header / fold selected header
1441 f : fold / unfold item, hiding/revealing its children
1441 f : fold / unfold item, hiding/revealing its children
1442 F : fold / unfold parent item and all of its ancestors
1442 F : fold / unfold parent item and all of its ancestors
1443 ctrl-l : scroll the selected line to the top of the screen
1443 ctrl-l : scroll the selected line to the top of the screen
1444 m : edit / resume editing the commit message
1444 m : edit / resume editing the commit message
1445 e : edit the currently selected hunk
1445 e : edit the currently selected hunk
1446 a : toggle amend mode, only with commit -i
1446 a : toggle amend mode, only with commit -i
1447 c : confirm selected changes
1447 c : confirm selected changes
1448 r : review/edit and confirm selected changes
1448 r : review/edit and confirm selected changes
1449 q : quit without confirming (no changes will be made)
1449 q : quit without confirming (no changes will be made)
1450 ? : help (what you're currently reading)""")
1450 ? : help (what you're currently reading)""")
1451
1451
1452 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1452 helpwin = curses.newwin(self.yscreensize, 0, 0, 0)
1453 helplines = helptext.split("\n")
1453 helplines = helptext.split("\n")
1454 helplines = helplines + [" "]*(
1454 helplines = helplines + [" "]*(
1455 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1455 self.yscreensize - self.numstatuslines - len(helplines) - 1)
1456 try:
1456 try:
1457 for line in helplines:
1457 for line in helplines:
1458 self.printstring(helpwin, line, pairname="legend")
1458 self.printstring(helpwin, line, pairname="legend")
1459 except curses.error:
1459 except curses.error:
1460 pass
1460 pass
1461 helpwin.refresh()
1461 helpwin.refresh()
1462 try:
1462 try:
1463 with self.ui.timeblockedsection('crecord'):
1463 with self.ui.timeblockedsection('crecord'):
1464 helpwin.getkey()
1464 helpwin.getkey()
1465 except curses.error:
1465 except curses.error:
1466 pass
1466 pass
1467
1467
1468 def commitMessageWindow(self):
1468 def commitMessageWindow(self):
1469 "Create a temporary commit message editing window on the screen."
1469 "Create a temporary commit message editing window on the screen."
1470
1470
1471 curses.raw()
1471 curses.raw()
1472 curses.def_prog_mode()
1472 curses.def_prog_mode()
1473 curses.endwin()
1473 curses.endwin()
1474 self.commenttext = self.ui.edit(self.commenttext, self.ui.username())
1474 self.commenttext = self.ui.edit(self.commenttext, self.ui.username())
1475 curses.cbreak()
1475 curses.cbreak()
1476 self.stdscr.refresh()
1476 self.stdscr.refresh()
1477 self.stdscr.keypad(1) # allow arrow-keys to continue to function
1477 self.stdscr.keypad(1) # allow arrow-keys to continue to function
1478
1478
1479 def confirmationwindow(self, windowtext):
1479 def confirmationwindow(self, windowtext):
1480 "display an informational window, then wait for and return a keypress."
1480 "display an informational window, then wait for and return a keypress."
1481
1481
1482 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1482 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
1483 try:
1483 try:
1484 lines = windowtext.split("\n")
1484 lines = windowtext.split("\n")
1485 for line in lines:
1485 for line in lines:
1486 self.printstring(confirmwin, line, pairname="selected")
1486 self.printstring(confirmwin, line, pairname="selected")
1487 except curses.error:
1487 except curses.error:
1488 pass
1488 pass
1489 self.stdscr.refresh()
1489 self.stdscr.refresh()
1490 confirmwin.refresh()
1490 confirmwin.refresh()
1491 try:
1491 try:
1492 with self.ui.timeblockedsection('crecord'):
1492 with self.ui.timeblockedsection('crecord'):
1493 response = chr(self.stdscr.getch())
1493 response = chr(self.stdscr.getch())
1494 except ValueError:
1494 except ValueError:
1495 response = None
1495 response = None
1496
1496
1497 return response
1497 return response
1498
1498
1499 def reviewcommit(self):
1499 def reviewcommit(self):
1500 """ask for 'y' to be pressed to confirm selected. return True if
1500 """ask for 'y' to be pressed to confirm selected. return True if
1501 confirmed."""
1501 confirmed."""
1502 confirmtext = _(
1502 confirmtext = _(
1503 """if you answer yes to the following, the your currently chosen patch chunks
1503 """if you answer yes to the following, the your currently chosen patch chunks
1504 will be loaded into an editor. you may modify the patch from the editor, and
1504 will be loaded into an editor. you may modify the patch from the editor, and
1505 save the changes if you wish to change the patch. otherwise, you can just
1505 save the changes if you wish to change the patch. otherwise, you can just
1506 close the editor without saving to accept the current patch as-is.
1506 close the editor without saving to accept the current patch as-is.
1507
1507
1508 note: don't add/remove lines unless you also modify the range information.
1508 note: don't add/remove lines unless you also modify the range information.
1509 failing to follow this rule will result in the commit aborting.
1509 failing to follow this rule will result in the commit aborting.
1510
1510
1511 are you sure you want to review/edit and confirm the selected changes [yn]?
1511 are you sure you want to review/edit and confirm the selected changes [yn]?
1512 """)
1512 """)
1513 with self.ui.timeblockedsection('crecord'):
1513 with self.ui.timeblockedsection('crecord'):
1514 response = self.confirmationwindow(confirmtext)
1514 response = self.confirmationwindow(confirmtext)
1515 if response is None:
1515 if response is None:
1516 response = "n"
1516 response = "n"
1517 if response.lower().startswith("y"):
1517 if response.lower().startswith("y"):
1518 return True
1518 return True
1519 else:
1519 else:
1520 return False
1520 return False
1521
1521
1522 def toggleamend(self, opts, test):
1522 def toggleamend(self, opts, test):
1523 """Toggle the amend flag.
1523 """Toggle the amend flag.
1524
1524
1525 When the amend flag is set, a commit will modify the most recently
1525 When the amend flag is set, a commit will modify the most recently
1526 committed changeset, instead of creating a new changeset. Otherwise, a
1526 committed changeset, instead of creating a new changeset. Otherwise, a
1527 new changeset will be created (the normal commit behavior).
1527 new changeset will be created (the normal commit behavior).
1528 """
1528 """
1529
1529
1530 try:
1530 try:
1531 ver = float(util.version()[:3])
1531 ver = float(util.version()[:3])
1532 except ValueError:
1532 except ValueError:
1533 ver = 1
1533 ver = 1
1534 if ver < 2.19:
1534 if ver < 2.19:
1535 msg = _("The amend option is unavailable with hg versions < 2.2\n\n"
1535 msg = _("The amend option is unavailable with hg versions < 2.2\n\n"
1536 "Press any key to continue.")
1536 "Press any key to continue.")
1537 elif opts.get('amend') is None:
1537 elif opts.get('amend') is None:
1538 opts['amend'] = True
1538 opts['amend'] = True
1539 msg = _("Amend option is turned on -- committing the currently "
1539 msg = _("Amend option is turned on -- committing the currently "
1540 "selected changes will not create a new changeset, but "
1540 "selected changes will not create a new changeset, but "
1541 "instead update the most recently committed changeset.\n\n"
1541 "instead update the most recently committed changeset.\n\n"
1542 "Press any key to continue.")
1542 "Press any key to continue.")
1543 elif opts.get('amend') is True:
1543 elif opts.get('amend') is True:
1544 opts['amend'] = None
1544 opts['amend'] = None
1545 msg = _("Amend option is turned off -- committing the currently "
1545 msg = _("Amend option is turned off -- committing the currently "
1546 "selected changes will create a new changeset.\n\n"
1546 "selected changes will create a new changeset.\n\n"
1547 "Press any key to continue.")
1547 "Press any key to continue.")
1548 if not test:
1548 if not test:
1549 self.confirmationwindow(msg)
1549 self.confirmationwindow(msg)
1550
1550
1551 def recenterdisplayedarea(self):
1551 def recenterdisplayedarea(self):
1552 """
1552 """
1553 once we scrolled with pg up pg down we can be pointing outside of the
1553 once we scrolled with pg up pg down we can be pointing outside of the
1554 display zone. we print the patch with towin=False to compute the
1554 display zone. we print the patch with towin=False to compute the
1555 location of the selected item even though it is outside of the displayed
1555 location of the selected item even though it is outside of the displayed
1556 zone and then update the scroll.
1556 zone and then update the scroll.
1557 """
1557 """
1558 self.printitem(towin=False)
1558 self.printitem(towin=False)
1559 self.updatescroll()
1559 self.updatescroll()
1560
1560
1561 def toggleedit(self, item=None, test=False):
1561 def toggleedit(self, item=None, test=False):
1562 """
1562 """
1563 edit the currently selected chunk
1563 edit the currently selected chunk
1564 """
1564 """
1565 def updateui(self):
1565 def updateui(self):
1566 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1566 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1567 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1567 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1568 self.updatescroll()
1568 self.updatescroll()
1569 self.stdscr.refresh()
1569 self.stdscr.refresh()
1570 self.statuswin.refresh()
1570 self.statuswin.refresh()
1571 self.stdscr.keypad(1)
1571 self.stdscr.keypad(1)
1572
1572
1573 def editpatchwitheditor(self, chunk):
1573 def editpatchwitheditor(self, chunk):
1574 if chunk is None:
1574 if chunk is None:
1575 self.ui.write(_('cannot edit patch for whole file'))
1575 self.ui.write(_('cannot edit patch for whole file'))
1576 self.ui.write("\n")
1576 self.ui.write("\n")
1577 return None
1577 return None
1578 if chunk.header.binary():
1578 if chunk.header.binary():
1579 self.ui.write(_('cannot edit patch for binary file'))
1579 self.ui.write(_('cannot edit patch for binary file'))
1580 self.ui.write("\n")
1580 self.ui.write("\n")
1581 return None
1581 return None
1582
1582
1583 # write the initial patch
1583 # write the initial patch
1584 patch = stringio()
1584 patch = stringio()
1585 patch.write(diffhelptext + hunkhelptext)
1585 patch.write(diffhelptext + hunkhelptext)
1586 chunk.header.write(patch)
1586 chunk.header.write(patch)
1587 chunk.write(patch)
1587 chunk.write(patch)
1588
1588
1589 # start the editor and wait for it to complete
1589 # start the editor and wait for it to complete
1590 try:
1590 try:
1591 patch = self.ui.edit(patch.getvalue(), "", action="diff")
1591 patch = self.ui.edit(patch.getvalue(), "", action="diff")
1592 except error.Abort as exc:
1592 except error.Abort as exc:
1593 self.errorstr = str(exc)
1593 self.errorstr = str(exc)
1594 return None
1594 return None
1595
1595
1596 # remove comment lines
1596 # remove comment lines
1597 patch = [line + '\n' for line in patch.splitlines()
1597 patch = [line + '\n' for line in patch.splitlines()
1598 if not line.startswith('#')]
1598 if not line.startswith('#')]
1599 return patchmod.parsepatch(patch)
1599 return patchmod.parsepatch(patch)
1600
1600
1601 if item is None:
1601 if item is None:
1602 item = self.currentselecteditem
1602 item = self.currentselecteditem
1603 if isinstance(item, uiheader):
1603 if isinstance(item, uiheader):
1604 return
1604 return
1605 if isinstance(item, uihunkline):
1605 if isinstance(item, uihunkline):
1606 item = item.parentitem()
1606 item = item.parentitem()
1607 if not isinstance(item, uihunk):
1607 if not isinstance(item, uihunk):
1608 return
1608 return
1609
1609
1610 # To go back to that hunk or its replacement at the end of the edit
1610 # To go back to that hunk or its replacement at the end of the edit
1611 itemindex = item.parentitem().hunks.index(item)
1611 itemindex = item.parentitem().hunks.index(item)
1612
1612
1613 beforeadded, beforeremoved = item.added, item.removed
1613 beforeadded, beforeremoved = item.added, item.removed
1614 newpatches = editpatchwitheditor(self, item)
1614 newpatches = editpatchwitheditor(self, item)
1615 if newpatches is None:
1615 if newpatches is None:
1616 if not test:
1616 if not test:
1617 updateui(self)
1617 updateui(self)
1618 return
1618 return
1619 header = item.header
1619 header = item.header
1620 editedhunkindex = header.hunks.index(item)
1620 editedhunkindex = header.hunks.index(item)
1621 hunksbefore = header.hunks[:editedhunkindex]
1621 hunksbefore = header.hunks[:editedhunkindex]
1622 hunksafter = header.hunks[editedhunkindex + 1:]
1622 hunksafter = header.hunks[editedhunkindex + 1:]
1623 newpatchheader = newpatches[0]
1623 newpatchheader = newpatches[0]
1624 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1624 newhunks = [uihunk(h, header) for h in newpatchheader.hunks]
1625 newadded = sum([h.added for h in newhunks])
1625 newadded = sum([h.added for h in newhunks])
1626 newremoved = sum([h.removed for h in newhunks])
1626 newremoved = sum([h.removed for h in newhunks])
1627 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1627 offset = (newadded - beforeadded) - (newremoved - beforeremoved)
1628
1628
1629 for h in hunksafter:
1629 for h in hunksafter:
1630 h.toline += offset
1630 h.toline += offset
1631 for h in newhunks:
1631 for h in newhunks:
1632 h.folded = False
1632 h.folded = False
1633 header.hunks = hunksbefore + newhunks + hunksafter
1633 header.hunks = hunksbefore + newhunks + hunksafter
1634 if self.emptypatch():
1634 if self.emptypatch():
1635 header.hunks = hunksbefore + [item] + hunksafter
1635 header.hunks = hunksbefore + [item] + hunksafter
1636 self.currentselecteditem = header
1636 self.currentselecteditem = header
1637 if len(header.hunks) > itemindex:
1637 if len(header.hunks) > itemindex:
1638 self.currentselecteditem = header.hunks[itemindex]
1638 self.currentselecteditem = header.hunks[itemindex]
1639
1639
1640 if not test:
1640 if not test:
1641 updateui(self)
1641 updateui(self)
1642
1642
1643 def emptypatch(self):
1643 def emptypatch(self):
1644 item = self.headerlist
1644 item = self.headerlist
1645 if not item:
1645 if not item:
1646 return True
1646 return True
1647 for header in item:
1647 for header in item:
1648 if header.hunks:
1648 if header.hunks:
1649 return False
1649 return False
1650 return True
1650 return True
1651
1651
1652 def handlekeypressed(self, keypressed, test=False):
1652 def handlekeypressed(self, keypressed, test=False):
1653 """
1653 """
1654 Perform actions based on pressed keys.
1654 Perform actions based on pressed keys.
1655
1655
1656 Return true to exit the main loop.
1656 Return true to exit the main loop.
1657 """
1657 """
1658 if keypressed in ["k", "KEY_UP"]:
1658 if keypressed in ["k", "KEY_UP"]:
1659 self.uparrowevent()
1659 self.uparrowevent()
1660 if keypressed in ["K", "KEY_PPAGE"]:
1660 if keypressed in ["K", "KEY_PPAGE"]:
1661 self.uparrowshiftevent()
1661 self.uparrowshiftevent()
1662 elif keypressed in ["j", "KEY_DOWN"]:
1662 elif keypressed in ["j", "KEY_DOWN"]:
1663 self.downarrowevent()
1663 self.downarrowevent()
1664 elif keypressed in ["J", "KEY_NPAGE"]:
1664 elif keypressed in ["J", "KEY_NPAGE"]:
1665 self.downarrowshiftevent()
1665 self.downarrowshiftevent()
1666 elif keypressed in ["l", "KEY_RIGHT"]:
1666 elif keypressed in ["l", "KEY_RIGHT"]:
1667 self.rightarrowevent()
1667 self.rightarrowevent()
1668 elif keypressed in ["h", "KEY_LEFT"]:
1668 elif keypressed in ["h", "KEY_LEFT"]:
1669 self.leftarrowevent()
1669 self.leftarrowevent()
1670 elif keypressed in ["H", "KEY_SLEFT"]:
1670 elif keypressed in ["H", "KEY_SLEFT"]:
1671 self.leftarrowshiftevent()
1671 self.leftarrowshiftevent()
1672 elif keypressed in ["q"]:
1672 elif keypressed in ["q"]:
1673 raise error.Abort(_('user quit'))
1673 raise error.Abort(_('user quit'))
1674 elif keypressed in ['a']:
1674 elif keypressed in ['a']:
1675 self.toggleamend(self.opts, test)
1675 self.toggleamend(self.opts, test)
1676 elif keypressed in ["c"]:
1676 elif keypressed in ["c"]:
1677 return True
1677 return True
1678 elif test and keypressed in ['X']:
1678 elif test and keypressed in ['X']:
1679 return True
1679 return True
1680 elif keypressed in ["r"]:
1680 elif keypressed in ["r"]:
1681 if self.reviewcommit():
1681 if self.reviewcommit():
1682 self.opts['review'] = True
1682 self.opts['review'] = True
1683 return True
1683 return True
1684 elif test and keypressed in ['R']:
1684 elif test and keypressed in ['R']:
1685 self.opts['review'] = True
1685 self.opts['review'] = True
1686 return True
1686 return True
1687 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1687 elif keypressed in [' '] or (test and keypressed in ["TOGGLE"]):
1688 self.toggleapply()
1688 self.toggleapply()
1689 if self.ui.configbool('experimental', 'spacemovesdown'):
1689 if self.ui.configbool('experimental', 'spacemovesdown'):
1690 self.downarrowevent()
1690 self.downarrowevent()
1691 elif keypressed in ['A']:
1691 elif keypressed in ['A']:
1692 self.toggleall()
1692 self.toggleall()
1693 elif keypressed in ['e']:
1693 elif keypressed in ['e']:
1694 self.toggleedit(test=test)
1694 self.toggleedit(test=test)
1695 elif keypressed in ["f"]:
1695 elif keypressed in ["f"]:
1696 self.togglefolded()
1696 self.togglefolded()
1697 elif keypressed in ["F"]:
1697 elif keypressed in ["F"]:
1698 self.togglefolded(foldparent=True)
1698 self.togglefolded(foldparent=True)
1699 elif keypressed in ["m"]:
1699 elif keypressed in ["m"]:
1700 self.commitMessageWindow()
1700 self.commitMessageWindow()
1701 elif keypressed in ["?"]:
1701 elif keypressed in ["?"]:
1702 self.helpwindow()
1702 self.helpwindow()
1703 self.stdscr.clear()
1703 self.stdscr.clear()
1704 self.stdscr.refresh()
1704 self.stdscr.refresh()
1705 elif curses.unctrl(keypressed) in ["^L"]:
1705 elif curses.unctrl(keypressed) in ["^L"]:
1706 # scroll the current line to the top of the screen
1706 # scroll the current line to the top of the screen
1707 self.scrolllines(self.selecteditemstartline)
1707 self.scrolllines(self.selecteditemstartline)
1708
1708
1709 def main(self, stdscr):
1709 def main(self, stdscr):
1710 """
1710 """
1711 method to be wrapped by curses.wrapper() for selecting chunks.
1711 method to be wrapped by curses.wrapper() for selecting chunks.
1712 """
1712 """
1713
1713
1714 origsigwinch = sentinel = object()
1714 origsigwinch = sentinel = object()
1715 if util.safehasattr(signal, 'SIGWINCH'):
1715 if util.safehasattr(signal, 'SIGWINCH'):
1716 origsigwinch = signal.signal(signal.SIGWINCH,
1716 origsigwinch = signal.signal(signal.SIGWINCH,
1717 self.sigwinchhandler)
1717 self.sigwinchhandler)
1718 try:
1718 try:
1719 return self._main(stdscr)
1719 return self._main(stdscr)
1720 finally:
1720 finally:
1721 if origsigwinch is not sentinel:
1721 if origsigwinch is not sentinel:
1722 signal.signal(signal.SIGWINCH, origsigwinch)
1722 signal.signal(signal.SIGWINCH, origsigwinch)
1723
1723
1724 def _main(self, stdscr):
1724 def _main(self, stdscr):
1725 self.stdscr = stdscr
1725 self.stdscr = stdscr
1726 # error during initialization, cannot be printed in the curses
1726 # error during initialization, cannot be printed in the curses
1727 # interface, it should be printed by the calling code
1727 # interface, it should be printed by the calling code
1728 self.initexc = None
1728 self.initexc = None
1729 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1729 self.yscreensize, self.xscreensize = self.stdscr.getmaxyx()
1730
1730
1731 curses.start_color()
1731 curses.start_color()
1732 try:
1732 try:
1733 curses.use_default_colors()
1733 curses.use_default_colors()
1734 except curses.error:
1734 except curses.error:
1735 self.usecolor = False
1735 self.usecolor = False
1736
1736
1737 # available colors: black, blue, cyan, green, magenta, white, yellow
1737 # available colors: black, blue, cyan, green, magenta, white, yellow
1738 # init_pair(color_id, foreground_color, background_color)
1738 # init_pair(color_id, foreground_color, background_color)
1739 self.initcolorpair(None, None, name="normal")
1739 self.initcolorpair(None, None, name="normal")
1740 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1740 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_MAGENTA,
1741 name="selected")
1741 name="selected")
1742 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1742 self.initcolorpair(curses.COLOR_RED, None, name="deletion")
1743 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1743 self.initcolorpair(curses.COLOR_GREEN, None, name="addition")
1744 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1744 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, name="legend")
1745 # newwin([height, width,] begin_y, begin_x)
1745 # newwin([height, width,] begin_y, begin_x)
1746 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1746 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
1747 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1747 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
1748
1748
1749 # figure out how much space to allocate for the chunk-pad which is
1749 # figure out how much space to allocate for the chunk-pad which is
1750 # used for displaying the patch
1750 # used for displaying the patch
1751
1751
1752 # stupid hack to prevent getnumlinesdisplayed from failing
1752 # stupid hack to prevent getnumlinesdisplayed from failing
1753 self.chunkpad = curses.newpad(1, self.xscreensize)
1753 self.chunkpad = curses.newpad(1, self.xscreensize)
1754
1754
1755 # add 1 so to account for last line text reaching end of line
1755 # add 1 so to account for last line text reaching end of line
1756 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1756 self.numpadlines = self.getnumlinesdisplayed(ignorefolding=True) + 1
1757
1757
1758 try:
1758 try:
1759 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1759 self.chunkpad = curses.newpad(self.numpadlines, self.xscreensize)
1760 except curses.error:
1760 except curses.error:
1761 self.initexc = fallbackerror(
1761 self.initexc = fallbackerror(
1762 _('this diff is too large to be displayed'))
1762 _('this diff is too large to be displayed'))
1763 return
1763 return
1764 # initialize selecteditemendline (initial start-line is 0)
1764 # initialize selecteditemendline (initial start-line is 0)
1765 self.selecteditemendline = self.getnumlinesdisplayed(
1765 self.selecteditemendline = self.getnumlinesdisplayed(
1766 self.currentselecteditem, recursechildren=False)
1766 self.currentselecteditem, recursechildren=False)
1767
1767
1768 while True:
1768 while True:
1769 self.updatescreen()
1769 self.updatescreen()
1770 try:
1770 try:
1771 with self.ui.timeblockedsection('crecord'):
1771 with self.ui.timeblockedsection('crecord'):
1772 keypressed = self.statuswin.getkey()
1772 keypressed = self.statuswin.getkey()
1773 if self.errorstr is not None:
1773 if self.errorstr is not None:
1774 self.errorstr = None
1774 self.errorstr = None
1775 continue
1775 continue
1776 except curses.error:
1776 except curses.error:
1777 keypressed = "foobar"
1777 keypressed = "foobar"
1778 if self.handlekeypressed(keypressed):
1778 if self.handlekeypressed(keypressed):
1779 break
1779 break
1780
1780
1781 if self.commenttext != "":
1781 if self.commenttext != "":
1782 whitespaceremoved = re.sub("(?m)^\s.*(\n|$)", "", self.commenttext)
1782 whitespaceremoved = re.sub("(?m)^\s.*(\n|$)", "", self.commenttext)
1783 if whitespaceremoved != "":
1783 if whitespaceremoved != "":
1784 self.opts['message'] = self.commenttext
1784 self.opts['message'] = self.commenttext
@@ -1,2232 +1,2234 b''
1 # merge.py - directory-level update/merge handling for Mercurial
1 # merge.py - directory-level update/merge handling for Mercurial
2 #
2 #
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
3 # Copyright 2006, 2007 Matt Mackall <mpm@selenic.com>
4 #
4 #
5 # This software may be used and distributed according to the terms of the
5 # This software may be used and distributed according to the terms of the
6 # GNU General Public License version 2 or any later version.
6 # GNU General Public License version 2 or any later version.
7
7
8 from __future__ import absolute_import
8 from __future__ import absolute_import
9
9
10 import errno
10 import errno
11 import hashlib
11 import hashlib
12 import shutil
12 import shutil
13 import struct
13 import struct
14
14
15 from .i18n import _
15 from .i18n import _
16 from .node import (
16 from .node import (
17 addednodeid,
17 addednodeid,
18 bin,
18 bin,
19 hex,
19 hex,
20 modifiednodeid,
20 modifiednodeid,
21 nullhex,
21 nullhex,
22 nullid,
22 nullid,
23 nullrev,
23 nullrev,
24 )
24 )
25 from .thirdparty import (
25 from .thirdparty import (
26 attr,
26 attr,
27 )
27 )
28 from . import (
28 from . import (
29 copies,
29 copies,
30 error,
30 error,
31 filemerge,
31 filemerge,
32 match as matchmod,
32 match as matchmod,
33 obsutil,
33 obsutil,
34 pycompat,
34 pycompat,
35 scmutil,
35 scmutil,
36 subrepoutil,
36 subrepoutil,
37 util,
37 util,
38 worker,
38 worker,
39 )
39 )
40
40
41 _pack = struct.pack
41 _pack = struct.pack
42 _unpack = struct.unpack
42 _unpack = struct.unpack
43
43
44 def _droponode(data):
44 def _droponode(data):
45 # used for compatibility for v1
45 # used for compatibility for v1
46 bits = data.split('\0')
46 bits = data.split('\0')
47 bits = bits[:-2] + bits[-1:]
47 bits = bits[:-2] + bits[-1:]
48 return '\0'.join(bits)
48 return '\0'.join(bits)
49
49
50 # Merge state record types. See ``mergestate`` docs for more.
50 # Merge state record types. See ``mergestate`` docs for more.
51 RECORD_LOCAL = b'L'
51 RECORD_LOCAL = b'L'
52 RECORD_OTHER = b'O'
52 RECORD_OTHER = b'O'
53 RECORD_MERGED = b'F'
53 RECORD_MERGED = b'F'
54 RECORD_CHANGEDELETE_CONFLICT = b'C'
54 RECORD_CHANGEDELETE_CONFLICT = b'C'
55 RECORD_MERGE_DRIVER_MERGE = b'D'
55 RECORD_MERGE_DRIVER_MERGE = b'D'
56 RECORD_PATH_CONFLICT = b'P'
56 RECORD_PATH_CONFLICT = b'P'
57 RECORD_MERGE_DRIVER_STATE = b'm'
57 RECORD_MERGE_DRIVER_STATE = b'm'
58 RECORD_FILE_VALUES = b'f'
58 RECORD_FILE_VALUES = b'f'
59 RECORD_LABELS = b'l'
59 RECORD_LABELS = b'l'
60 RECORD_OVERRIDE = b't'
60 RECORD_OVERRIDE = b't'
61 RECORD_UNSUPPORTED_MANDATORY = b'X'
61 RECORD_UNSUPPORTED_MANDATORY = b'X'
62 RECORD_UNSUPPORTED_ADVISORY = b'x'
62 RECORD_UNSUPPORTED_ADVISORY = b'x'
63
63
64 MERGE_DRIVER_STATE_UNMARKED = b'u'
64 MERGE_DRIVER_STATE_UNMARKED = b'u'
65 MERGE_DRIVER_STATE_MARKED = b'm'
65 MERGE_DRIVER_STATE_MARKED = b'm'
66 MERGE_DRIVER_STATE_SUCCESS = b's'
66 MERGE_DRIVER_STATE_SUCCESS = b's'
67
67
68 MERGE_RECORD_UNRESOLVED = b'u'
68 MERGE_RECORD_UNRESOLVED = b'u'
69 MERGE_RECORD_RESOLVED = b'r'
69 MERGE_RECORD_RESOLVED = b'r'
70 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
70 MERGE_RECORD_UNRESOLVED_PATH = b'pu'
71 MERGE_RECORD_RESOLVED_PATH = b'pr'
71 MERGE_RECORD_RESOLVED_PATH = b'pr'
72 MERGE_RECORD_DRIVER_RESOLVED = b'd'
72 MERGE_RECORD_DRIVER_RESOLVED = b'd'
73
73
74 ACTION_FORGET = b'f'
74 ACTION_FORGET = b'f'
75 ACTION_REMOVE = b'r'
75 ACTION_REMOVE = b'r'
76 ACTION_ADD = b'a'
76 ACTION_ADD = b'a'
77 ACTION_GET = b'g'
77 ACTION_GET = b'g'
78 ACTION_PATH_CONFLICT = b'p'
78 ACTION_PATH_CONFLICT = b'p'
79 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
79 ACTION_PATH_CONFLICT_RESOLVE = b'pr'
80 ACTION_ADD_MODIFIED = b'am'
80 ACTION_ADD_MODIFIED = b'am'
81 ACTION_CREATED = b'c'
81 ACTION_CREATED = b'c'
82 ACTION_DELETED_CHANGED = b'dc'
82 ACTION_DELETED_CHANGED = b'dc'
83 ACTION_CHANGED_DELETED = b'cd'
83 ACTION_CHANGED_DELETED = b'cd'
84 ACTION_MERGE = b'm'
84 ACTION_MERGE = b'm'
85 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
85 ACTION_LOCAL_DIR_RENAME_GET = b'dg'
86 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
86 ACTION_DIR_RENAME_MOVE_LOCAL = b'dm'
87 ACTION_KEEP = b'k'
87 ACTION_KEEP = b'k'
88 ACTION_EXEC = b'e'
88 ACTION_EXEC = b'e'
89 ACTION_CREATED_MERGE = b'cm'
89 ACTION_CREATED_MERGE = b'cm'
90
90
91 class mergestate(object):
91 class mergestate(object):
92 '''track 3-way merge state of individual files
92 '''track 3-way merge state of individual files
93
93
94 The merge state is stored on disk when needed. Two files are used: one with
94 The merge state is stored on disk when needed. Two files are used: one with
95 an old format (version 1), and one with a new format (version 2). Version 2
95 an old format (version 1), and one with a new format (version 2). Version 2
96 stores a superset of the data in version 1, including new kinds of records
96 stores a superset of the data in version 1, including new kinds of records
97 in the future. For more about the new format, see the documentation for
97 in the future. For more about the new format, see the documentation for
98 `_readrecordsv2`.
98 `_readrecordsv2`.
99
99
100 Each record can contain arbitrary content, and has an associated type. This
100 Each record can contain arbitrary content, and has an associated type. This
101 `type` should be a letter. If `type` is uppercase, the record is mandatory:
101 `type` should be a letter. If `type` is uppercase, the record is mandatory:
102 versions of Mercurial that don't support it should abort. If `type` is
102 versions of Mercurial that don't support it should abort. If `type` is
103 lowercase, the record can be safely ignored.
103 lowercase, the record can be safely ignored.
104
104
105 Currently known records:
105 Currently known records:
106
106
107 L: the node of the "local" part of the merge (hexified version)
107 L: the node of the "local" part of the merge (hexified version)
108 O: the node of the "other" part of the merge (hexified version)
108 O: the node of the "other" part of the merge (hexified version)
109 F: a file to be merged entry
109 F: a file to be merged entry
110 C: a change/delete or delete/change conflict
110 C: a change/delete or delete/change conflict
111 D: a file that the external merge driver will merge internally
111 D: a file that the external merge driver will merge internally
112 (experimental)
112 (experimental)
113 P: a path conflict (file vs directory)
113 P: a path conflict (file vs directory)
114 m: the external merge driver defined for this merge plus its run state
114 m: the external merge driver defined for this merge plus its run state
115 (experimental)
115 (experimental)
116 f: a (filename, dictionary) tuple of optional values for a given file
116 f: a (filename, dictionary) tuple of optional values for a given file
117 X: unsupported mandatory record type (used in tests)
117 X: unsupported mandatory record type (used in tests)
118 x: unsupported advisory record type (used in tests)
118 x: unsupported advisory record type (used in tests)
119 l: the labels for the parts of the merge.
119 l: the labels for the parts of the merge.
120
120
121 Merge driver run states (experimental):
121 Merge driver run states (experimental):
122 u: driver-resolved files unmarked -- needs to be run next time we're about
122 u: driver-resolved files unmarked -- needs to be run next time we're about
123 to resolve or commit
123 to resolve or commit
124 m: driver-resolved files marked -- only needs to be run before commit
124 m: driver-resolved files marked -- only needs to be run before commit
125 s: success/skipped -- does not need to be run any more
125 s: success/skipped -- does not need to be run any more
126
126
127 Merge record states (stored in self._state, indexed by filename):
127 Merge record states (stored in self._state, indexed by filename):
128 u: unresolved conflict
128 u: unresolved conflict
129 r: resolved conflict
129 r: resolved conflict
130 pu: unresolved path conflict (file conflicts with directory)
130 pu: unresolved path conflict (file conflicts with directory)
131 pr: resolved path conflict
131 pr: resolved path conflict
132 d: driver-resolved conflict
132 d: driver-resolved conflict
133
133
134 The resolve command transitions between 'u' and 'r' for conflicts and
134 The resolve command transitions between 'u' and 'r' for conflicts and
135 'pu' and 'pr' for path conflicts.
135 'pu' and 'pr' for path conflicts.
136 '''
136 '''
137 statepathv1 = 'merge/state'
137 statepathv1 = 'merge/state'
138 statepathv2 = 'merge/state2'
138 statepathv2 = 'merge/state2'
139
139
140 @staticmethod
140 @staticmethod
141 def clean(repo, node=None, other=None, labels=None):
141 def clean(repo, node=None, other=None, labels=None):
142 """Initialize a brand new merge state, removing any existing state on
142 """Initialize a brand new merge state, removing any existing state on
143 disk."""
143 disk."""
144 ms = mergestate(repo)
144 ms = mergestate(repo)
145 ms.reset(node, other, labels)
145 ms.reset(node, other, labels)
146 return ms
146 return ms
147
147
148 @staticmethod
148 @staticmethod
149 def read(repo):
149 def read(repo):
150 """Initialize the merge state, reading it from disk."""
150 """Initialize the merge state, reading it from disk."""
151 ms = mergestate(repo)
151 ms = mergestate(repo)
152 ms._read()
152 ms._read()
153 return ms
153 return ms
154
154
155 def __init__(self, repo):
155 def __init__(self, repo):
156 """Initialize the merge state.
156 """Initialize the merge state.
157
157
158 Do not use this directly! Instead call read() or clean()."""
158 Do not use this directly! Instead call read() or clean()."""
159 self._repo = repo
159 self._repo = repo
160 self._dirty = False
160 self._dirty = False
161 self._labels = None
161 self._labels = None
162
162
163 def reset(self, node=None, other=None, labels=None):
163 def reset(self, node=None, other=None, labels=None):
164 self._state = {}
164 self._state = {}
165 self._stateextras = {}
165 self._stateextras = {}
166 self._local = None
166 self._local = None
167 self._other = None
167 self._other = None
168 self._labels = labels
168 self._labels = labels
169 for var in ('localctx', 'otherctx'):
169 for var in ('localctx', 'otherctx'):
170 if var in vars(self):
170 if var in vars(self):
171 delattr(self, var)
171 delattr(self, var)
172 if node:
172 if node:
173 self._local = node
173 self._local = node
174 self._other = other
174 self._other = other
175 self._readmergedriver = None
175 self._readmergedriver = None
176 if self.mergedriver:
176 if self.mergedriver:
177 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
177 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
178 else:
178 else:
179 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
179 self._mdstate = MERGE_DRIVER_STATE_UNMARKED
180 shutil.rmtree(self._repo.vfs.join('merge'), True)
180 shutil.rmtree(self._repo.vfs.join('merge'), True)
181 self._results = {}
181 self._results = {}
182 self._dirty = False
182 self._dirty = False
183
183
184 def _read(self):
184 def _read(self):
185 """Analyse each record content to restore a serialized state from disk
185 """Analyse each record content to restore a serialized state from disk
186
186
187 This function process "record" entry produced by the de-serialization
187 This function process "record" entry produced by the de-serialization
188 of on disk file.
188 of on disk file.
189 """
189 """
190 self._state = {}
190 self._state = {}
191 self._stateextras = {}
191 self._stateextras = {}
192 self._local = None
192 self._local = None
193 self._other = None
193 self._other = None
194 for var in ('localctx', 'otherctx'):
194 for var in ('localctx', 'otherctx'):
195 if var in vars(self):
195 if var in vars(self):
196 delattr(self, var)
196 delattr(self, var)
197 self._readmergedriver = None
197 self._readmergedriver = None
198 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
198 self._mdstate = MERGE_DRIVER_STATE_SUCCESS
199 unsupported = set()
199 unsupported = set()
200 records = self._readrecords()
200 records = self._readrecords()
201 for rtype, record in records:
201 for rtype, record in records:
202 if rtype == RECORD_LOCAL:
202 if rtype == RECORD_LOCAL:
203 self._local = bin(record)
203 self._local = bin(record)
204 elif rtype == RECORD_OTHER:
204 elif rtype == RECORD_OTHER:
205 self._other = bin(record)
205 self._other = bin(record)
206 elif rtype == RECORD_MERGE_DRIVER_STATE:
206 elif rtype == RECORD_MERGE_DRIVER_STATE:
207 bits = record.split('\0', 1)
207 bits = record.split('\0', 1)
208 mdstate = bits[1]
208 mdstate = bits[1]
209 if len(mdstate) != 1 or mdstate not in (
209 if len(mdstate) != 1 or mdstate not in (
210 MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
210 MERGE_DRIVER_STATE_UNMARKED, MERGE_DRIVER_STATE_MARKED,
211 MERGE_DRIVER_STATE_SUCCESS):
211 MERGE_DRIVER_STATE_SUCCESS):
212 # the merge driver should be idempotent, so just rerun it
212 # the merge driver should be idempotent, so just rerun it
213 mdstate = MERGE_DRIVER_STATE_UNMARKED
213 mdstate = MERGE_DRIVER_STATE_UNMARKED
214
214
215 self._readmergedriver = bits[0]
215 self._readmergedriver = bits[0]
216 self._mdstate = mdstate
216 self._mdstate = mdstate
217 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
217 elif rtype in (RECORD_MERGED, RECORD_CHANGEDELETE_CONFLICT,
218 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
218 RECORD_PATH_CONFLICT, RECORD_MERGE_DRIVER_MERGE):
219 bits = record.split('\0')
219 bits = record.split('\0')
220 self._state[bits[0]] = bits[1:]
220 self._state[bits[0]] = bits[1:]
221 elif rtype == RECORD_FILE_VALUES:
221 elif rtype == RECORD_FILE_VALUES:
222 filename, rawextras = record.split('\0', 1)
222 filename, rawextras = record.split('\0', 1)
223 extraparts = rawextras.split('\0')
223 extraparts = rawextras.split('\0')
224 extras = {}
224 extras = {}
225 i = 0
225 i = 0
226 while i < len(extraparts):
226 while i < len(extraparts):
227 extras[extraparts[i]] = extraparts[i + 1]
227 extras[extraparts[i]] = extraparts[i + 1]
228 i += 2
228 i += 2
229
229
230 self._stateextras[filename] = extras
230 self._stateextras[filename] = extras
231 elif rtype == RECORD_LABELS:
231 elif rtype == RECORD_LABELS:
232 labels = record.split('\0', 2)
232 labels = record.split('\0', 2)
233 self._labels = [l for l in labels if len(l) > 0]
233 self._labels = [l for l in labels if len(l) > 0]
234 elif not rtype.islower():
234 elif not rtype.islower():
235 unsupported.add(rtype)
235 unsupported.add(rtype)
236 self._results = {}
236 self._results = {}
237 self._dirty = False
237 self._dirty = False
238
238
239 if unsupported:
239 if unsupported:
240 raise error.UnsupportedMergeRecords(unsupported)
240 raise error.UnsupportedMergeRecords(unsupported)
241
241
242 def _readrecords(self):
242 def _readrecords(self):
243 """Read merge state from disk and return a list of record (TYPE, data)
243 """Read merge state from disk and return a list of record (TYPE, data)
244
244
245 We read data from both v1 and v2 files and decide which one to use.
245 We read data from both v1 and v2 files and decide which one to use.
246
246
247 V1 has been used by version prior to 2.9.1 and contains less data than
247 V1 has been used by version prior to 2.9.1 and contains less data than
248 v2. We read both versions and check if no data in v2 contradicts
248 v2. We read both versions and check if no data in v2 contradicts
249 v1. If there is not contradiction we can safely assume that both v1
249 v1. If there is not contradiction we can safely assume that both v1
250 and v2 were written at the same time and use the extract data in v2. If
250 and v2 were written at the same time and use the extract data in v2. If
251 there is contradiction we ignore v2 content as we assume an old version
251 there is contradiction we ignore v2 content as we assume an old version
252 of Mercurial has overwritten the mergestate file and left an old v2
252 of Mercurial has overwritten the mergestate file and left an old v2
253 file around.
253 file around.
254
254
255 returns list of record [(TYPE, data), ...]"""
255 returns list of record [(TYPE, data), ...]"""
256 v1records = self._readrecordsv1()
256 v1records = self._readrecordsv1()
257 v2records = self._readrecordsv2()
257 v2records = self._readrecordsv2()
258 if self._v1v2match(v1records, v2records):
258 if self._v1v2match(v1records, v2records):
259 return v2records
259 return v2records
260 else:
260 else:
261 # v1 file is newer than v2 file, use it
261 # v1 file is newer than v2 file, use it
262 # we have to infer the "other" changeset of the merge
262 # we have to infer the "other" changeset of the merge
263 # we cannot do better than that with v1 of the format
263 # we cannot do better than that with v1 of the format
264 mctx = self._repo[None].parents()[-1]
264 mctx = self._repo[None].parents()[-1]
265 v1records.append((RECORD_OTHER, mctx.hex()))
265 v1records.append((RECORD_OTHER, mctx.hex()))
266 # add place holder "other" file node information
266 # add place holder "other" file node information
267 # nobody is using it yet so we do no need to fetch the data
267 # nobody is using it yet so we do no need to fetch the data
268 # if mctx was wrong `mctx[bits[-2]]` may fails.
268 # if mctx was wrong `mctx[bits[-2]]` may fails.
269 for idx, r in enumerate(v1records):
269 for idx, r in enumerate(v1records):
270 if r[0] == RECORD_MERGED:
270 if r[0] == RECORD_MERGED:
271 bits = r[1].split('\0')
271 bits = r[1].split('\0')
272 bits.insert(-2, '')
272 bits.insert(-2, '')
273 v1records[idx] = (r[0], '\0'.join(bits))
273 v1records[idx] = (r[0], '\0'.join(bits))
274 return v1records
274 return v1records
275
275
276 def _v1v2match(self, v1records, v2records):
276 def _v1v2match(self, v1records, v2records):
277 oldv2 = set() # old format version of v2 record
277 oldv2 = set() # old format version of v2 record
278 for rec in v2records:
278 for rec in v2records:
279 if rec[0] == RECORD_LOCAL:
279 if rec[0] == RECORD_LOCAL:
280 oldv2.add(rec)
280 oldv2.add(rec)
281 elif rec[0] == RECORD_MERGED:
281 elif rec[0] == RECORD_MERGED:
282 # drop the onode data (not contained in v1)
282 # drop the onode data (not contained in v1)
283 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
283 oldv2.add((RECORD_MERGED, _droponode(rec[1])))
284 for rec in v1records:
284 for rec in v1records:
285 if rec not in oldv2:
285 if rec not in oldv2:
286 return False
286 return False
287 else:
287 else:
288 return True
288 return True
289
289
290 def _readrecordsv1(self):
290 def _readrecordsv1(self):
291 """read on disk merge state for version 1 file
291 """read on disk merge state for version 1 file
292
292
293 returns list of record [(TYPE, data), ...]
293 returns list of record [(TYPE, data), ...]
294
294
295 Note: the "F" data from this file are one entry short
295 Note: the "F" data from this file are one entry short
296 (no "other file node" entry)
296 (no "other file node" entry)
297 """
297 """
298 records = []
298 records = []
299 try:
299 try:
300 f = self._repo.vfs(self.statepathv1)
300 f = self._repo.vfs(self.statepathv1)
301 for i, l in enumerate(f):
301 for i, l in enumerate(f):
302 if i == 0:
302 if i == 0:
303 records.append((RECORD_LOCAL, l[:-1]))
303 records.append((RECORD_LOCAL, l[:-1]))
304 else:
304 else:
305 records.append((RECORD_MERGED, l[:-1]))
305 records.append((RECORD_MERGED, l[:-1]))
306 f.close()
306 f.close()
307 except IOError as err:
307 except IOError as err:
308 if err.errno != errno.ENOENT:
308 if err.errno != errno.ENOENT:
309 raise
309 raise
310 return records
310 return records
311
311
312 def _readrecordsv2(self):
312 def _readrecordsv2(self):
313 """read on disk merge state for version 2 file
313 """read on disk merge state for version 2 file
314
314
315 This format is a list of arbitrary records of the form:
315 This format is a list of arbitrary records of the form:
316
316
317 [type][length][content]
317 [type][length][content]
318
318
319 `type` is a single character, `length` is a 4 byte integer, and
319 `type` is a single character, `length` is a 4 byte integer, and
320 `content` is an arbitrary byte sequence of length `length`.
320 `content` is an arbitrary byte sequence of length `length`.
321
321
322 Mercurial versions prior to 3.7 have a bug where if there are
322 Mercurial versions prior to 3.7 have a bug where if there are
323 unsupported mandatory merge records, attempting to clear out the merge
323 unsupported mandatory merge records, attempting to clear out the merge
324 state with hg update --clean or similar aborts. The 't' record type
324 state with hg update --clean or similar aborts. The 't' record type
325 works around that by writing out what those versions treat as an
325 works around that by writing out what those versions treat as an
326 advisory record, but later versions interpret as special: the first
326 advisory record, but later versions interpret as special: the first
327 character is the 'real' record type and everything onwards is the data.
327 character is the 'real' record type and everything onwards is the data.
328
328
329 Returns list of records [(TYPE, data), ...]."""
329 Returns list of records [(TYPE, data), ...]."""
330 records = []
330 records = []
331 try:
331 try:
332 f = self._repo.vfs(self.statepathv2)
332 f = self._repo.vfs(self.statepathv2)
333 data = f.read()
333 data = f.read()
334 off = 0
334 off = 0
335 end = len(data)
335 end = len(data)
336 while off < end:
336 while off < end:
337 rtype = data[off:off + 1]
337 rtype = data[off:off + 1]
338 off += 1
338 off += 1
339 length = _unpack('>I', data[off:(off + 4)])[0]
339 length = _unpack('>I', data[off:(off + 4)])[0]
340 off += 4
340 off += 4
341 record = data[off:(off + length)]
341 record = data[off:(off + length)]
342 off += length
342 off += length
343 if rtype == RECORD_OVERRIDE:
343 if rtype == RECORD_OVERRIDE:
344 rtype, record = record[0:1], record[1:]
344 rtype, record = record[0:1], record[1:]
345 records.append((rtype, record))
345 records.append((rtype, record))
346 f.close()
346 f.close()
347 except IOError as err:
347 except IOError as err:
348 if err.errno != errno.ENOENT:
348 if err.errno != errno.ENOENT:
349 raise
349 raise
350 return records
350 return records
351
351
352 @util.propertycache
352 @util.propertycache
353 def mergedriver(self):
353 def mergedriver(self):
354 # protect against the following:
354 # protect against the following:
355 # - A configures a malicious merge driver in their hgrc, then
355 # - A configures a malicious merge driver in their hgrc, then
356 # pauses the merge
356 # pauses the merge
357 # - A edits their hgrc to remove references to the merge driver
357 # - A edits their hgrc to remove references to the merge driver
358 # - A gives a copy of their entire repo, including .hg, to B
358 # - A gives a copy of their entire repo, including .hg, to B
359 # - B inspects .hgrc and finds it to be clean
359 # - B inspects .hgrc and finds it to be clean
360 # - B then continues the merge and the malicious merge driver
360 # - B then continues the merge and the malicious merge driver
361 # gets invoked
361 # gets invoked
362 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
362 configmergedriver = self._repo.ui.config('experimental', 'mergedriver')
363 if (self._readmergedriver is not None
363 if (self._readmergedriver is not None
364 and self._readmergedriver != configmergedriver):
364 and self._readmergedriver != configmergedriver):
365 raise error.ConfigError(
365 raise error.ConfigError(
366 _("merge driver changed since merge started"),
366 _("merge driver changed since merge started"),
367 hint=_("revert merge driver change or abort merge"))
367 hint=_("revert merge driver change or abort merge"))
368
368
369 return configmergedriver
369 return configmergedriver
370
370
371 @util.propertycache
371 @util.propertycache
372 def localctx(self):
372 def localctx(self):
373 if self._local is None:
373 if self._local is None:
374 msg = "localctx accessed but self._local isn't set"
374 msg = "localctx accessed but self._local isn't set"
375 raise error.ProgrammingError(msg)
375 raise error.ProgrammingError(msg)
376 return self._repo[self._local]
376 return self._repo[self._local]
377
377
378 @util.propertycache
378 @util.propertycache
379 def otherctx(self):
379 def otherctx(self):
380 if self._other is None:
380 if self._other is None:
381 msg = "otherctx accessed but self._other isn't set"
381 msg = "otherctx accessed but self._other isn't set"
382 raise error.ProgrammingError(msg)
382 raise error.ProgrammingError(msg)
383 return self._repo[self._other]
383 return self._repo[self._other]
384
384
385 def active(self):
385 def active(self):
386 """Whether mergestate is active.
386 """Whether mergestate is active.
387
387
388 Returns True if there appears to be mergestate. This is a rough proxy
388 Returns True if there appears to be mergestate. This is a rough proxy
389 for "is a merge in progress."
389 for "is a merge in progress."
390 """
390 """
391 # Check local variables before looking at filesystem for performance
391 # Check local variables before looking at filesystem for performance
392 # reasons.
392 # reasons.
393 return bool(self._local) or bool(self._state) or \
393 return bool(self._local) or bool(self._state) or \
394 self._repo.vfs.exists(self.statepathv1) or \
394 self._repo.vfs.exists(self.statepathv1) or \
395 self._repo.vfs.exists(self.statepathv2)
395 self._repo.vfs.exists(self.statepathv2)
396
396
397 def commit(self):
397 def commit(self):
398 """Write current state on disk (if necessary)"""
398 """Write current state on disk (if necessary)"""
399 if self._dirty:
399 if self._dirty:
400 records = self._makerecords()
400 records = self._makerecords()
401 self._writerecords(records)
401 self._writerecords(records)
402 self._dirty = False
402 self._dirty = False
403
403
404 def _makerecords(self):
404 def _makerecords(self):
405 records = []
405 records = []
406 records.append((RECORD_LOCAL, hex(self._local)))
406 records.append((RECORD_LOCAL, hex(self._local)))
407 records.append((RECORD_OTHER, hex(self._other)))
407 records.append((RECORD_OTHER, hex(self._other)))
408 if self.mergedriver:
408 if self.mergedriver:
409 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
409 records.append((RECORD_MERGE_DRIVER_STATE, '\0'.join([
410 self.mergedriver, self._mdstate])))
410 self.mergedriver, self._mdstate])))
411 # Write out state items. In all cases, the value of the state map entry
411 # Write out state items. In all cases, the value of the state map entry
412 # is written as the contents of the record. The record type depends on
412 # is written as the contents of the record. The record type depends on
413 # the type of state that is stored, and capital-letter records are used
413 # the type of state that is stored, and capital-letter records are used
414 # to prevent older versions of Mercurial that do not support the feature
414 # to prevent older versions of Mercurial that do not support the feature
415 # from loading them.
415 # from loading them.
416 for filename, v in self._state.iteritems():
416 for filename, v in self._state.iteritems():
417 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
417 if v[0] == MERGE_RECORD_DRIVER_RESOLVED:
418 # Driver-resolved merge. These are stored in 'D' records.
418 # Driver-resolved merge. These are stored in 'D' records.
419 records.append((RECORD_MERGE_DRIVER_MERGE,
419 records.append((RECORD_MERGE_DRIVER_MERGE,
420 '\0'.join([filename] + v)))
420 '\0'.join([filename] + v)))
421 elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
421 elif v[0] in (MERGE_RECORD_UNRESOLVED_PATH,
422 MERGE_RECORD_RESOLVED_PATH):
422 MERGE_RECORD_RESOLVED_PATH):
423 # Path conflicts. These are stored in 'P' records. The current
423 # Path conflicts. These are stored in 'P' records. The current
424 # resolution state ('pu' or 'pr') is stored within the record.
424 # resolution state ('pu' or 'pr') is stored within the record.
425 records.append((RECORD_PATH_CONFLICT,
425 records.append((RECORD_PATH_CONFLICT,
426 '\0'.join([filename] + v)))
426 '\0'.join([filename] + v)))
427 elif v[1] == nullhex or v[6] == nullhex:
427 elif v[1] == nullhex or v[6] == nullhex:
428 # Change/Delete or Delete/Change conflicts. These are stored in
428 # Change/Delete or Delete/Change conflicts. These are stored in
429 # 'C' records. v[1] is the local file, and is nullhex when the
429 # 'C' records. v[1] is the local file, and is nullhex when the
430 # file is deleted locally ('dc'). v[6] is the remote file, and
430 # file is deleted locally ('dc'). v[6] is the remote file, and
431 # is nullhex when the file is deleted remotely ('cd').
431 # is nullhex when the file is deleted remotely ('cd').
432 records.append((RECORD_CHANGEDELETE_CONFLICT,
432 records.append((RECORD_CHANGEDELETE_CONFLICT,
433 '\0'.join([filename] + v)))
433 '\0'.join([filename] + v)))
434 else:
434 else:
435 # Normal files. These are stored in 'F' records.
435 # Normal files. These are stored in 'F' records.
436 records.append((RECORD_MERGED,
436 records.append((RECORD_MERGED,
437 '\0'.join([filename] + v)))
437 '\0'.join([filename] + v)))
438 for filename, extras in sorted(self._stateextras.iteritems()):
438 for filename, extras in sorted(self._stateextras.iteritems()):
439 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
439 rawextras = '\0'.join('%s\0%s' % (k, v) for k, v in
440 extras.iteritems())
440 extras.iteritems())
441 records.append((RECORD_FILE_VALUES,
441 records.append((RECORD_FILE_VALUES,
442 '%s\0%s' % (filename, rawextras)))
442 '%s\0%s' % (filename, rawextras)))
443 if self._labels is not None:
443 if self._labels is not None:
444 labels = '\0'.join(self._labels)
444 labels = '\0'.join(self._labels)
445 records.append((RECORD_LABELS, labels))
445 records.append((RECORD_LABELS, labels))
446 return records
446 return records
447
447
448 def _writerecords(self, records):
448 def _writerecords(self, records):
449 """Write current state on disk (both v1 and v2)"""
449 """Write current state on disk (both v1 and v2)"""
450 self._writerecordsv1(records)
450 self._writerecordsv1(records)
451 self._writerecordsv2(records)
451 self._writerecordsv2(records)
452
452
453 def _writerecordsv1(self, records):
453 def _writerecordsv1(self, records):
454 """Write current state on disk in a version 1 file"""
454 """Write current state on disk in a version 1 file"""
455 f = self._repo.vfs(self.statepathv1, 'wb')
455 f = self._repo.vfs(self.statepathv1, 'wb')
456 irecords = iter(records)
456 irecords = iter(records)
457 lrecords = next(irecords)
457 lrecords = next(irecords)
458 assert lrecords[0] == RECORD_LOCAL
458 assert lrecords[0] == RECORD_LOCAL
459 f.write(hex(self._local) + '\n')
459 f.write(hex(self._local) + '\n')
460 for rtype, data in irecords:
460 for rtype, data in irecords:
461 if rtype == RECORD_MERGED:
461 if rtype == RECORD_MERGED:
462 f.write('%s\n' % _droponode(data))
462 f.write('%s\n' % _droponode(data))
463 f.close()
463 f.close()
464
464
465 def _writerecordsv2(self, records):
465 def _writerecordsv2(self, records):
466 """Write current state on disk in a version 2 file
466 """Write current state on disk in a version 2 file
467
467
468 See the docstring for _readrecordsv2 for why we use 't'."""
468 See the docstring for _readrecordsv2 for why we use 't'."""
469 # these are the records that all version 2 clients can read
469 # these are the records that all version 2 clients can read
470 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
470 allowlist = (RECORD_LOCAL, RECORD_OTHER, RECORD_MERGED)
471 f = self._repo.vfs(self.statepathv2, 'wb')
471 f = self._repo.vfs(self.statepathv2, 'wb')
472 for key, data in records:
472 for key, data in records:
473 assert len(key) == 1
473 assert len(key) == 1
474 if key not in allowlist:
474 if key not in allowlist:
475 key, data = RECORD_OVERRIDE, '%s%s' % (key, data)
475 key, data = RECORD_OVERRIDE, '%s%s' % (key, data)
476 format = '>sI%is' % len(data)
476 format = '>sI%is' % len(data)
477 f.write(_pack(format, key, len(data), data))
477 f.write(_pack(format, key, len(data), data))
478 f.close()
478 f.close()
479
479
480 def add(self, fcl, fco, fca, fd):
480 def add(self, fcl, fco, fca, fd):
481 """add a new (potentially?) conflicting file the merge state
481 """add a new (potentially?) conflicting file the merge state
482 fcl: file context for local,
482 fcl: file context for local,
483 fco: file context for remote,
483 fco: file context for remote,
484 fca: file context for ancestors,
484 fca: file context for ancestors,
485 fd: file path of the resulting merge.
485 fd: file path of the resulting merge.
486
486
487 note: also write the local version to the `.hg/merge` directory.
487 note: also write the local version to the `.hg/merge` directory.
488 """
488 """
489 if fcl.isabsent():
489 if fcl.isabsent():
490 hash = nullhex
490 hash = nullhex
491 else:
491 else:
492 hash = hex(hashlib.sha1(fcl.path()).digest())
492 hash = hex(hashlib.sha1(fcl.path()).digest())
493 self._repo.vfs.write('merge/' + hash, fcl.data())
493 self._repo.vfs.write('merge/' + hash, fcl.data())
494 self._state[fd] = [MERGE_RECORD_UNRESOLVED, hash, fcl.path(),
494 self._state[fd] = [MERGE_RECORD_UNRESOLVED, hash, fcl.path(),
495 fca.path(), hex(fca.filenode()),
495 fca.path(), hex(fca.filenode()),
496 fco.path(), hex(fco.filenode()),
496 fco.path(), hex(fco.filenode()),
497 fcl.flags()]
497 fcl.flags()]
498 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
498 self._stateextras[fd] = {'ancestorlinknode': hex(fca.node())}
499 self._dirty = True
499 self._dirty = True
500
500
501 def addpath(self, path, frename, forigin):
501 def addpath(self, path, frename, forigin):
502 """add a new conflicting path to the merge state
502 """add a new conflicting path to the merge state
503 path: the path that conflicts
503 path: the path that conflicts
504 frename: the filename the conflicting file was renamed to
504 frename: the filename the conflicting file was renamed to
505 forigin: origin of the file ('l' or 'r' for local/remote)
505 forigin: origin of the file ('l' or 'r' for local/remote)
506 """
506 """
507 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
507 self._state[path] = [MERGE_RECORD_UNRESOLVED_PATH, frename, forigin]
508 self._dirty = True
508 self._dirty = True
509
509
510 def __contains__(self, dfile):
510 def __contains__(self, dfile):
511 return dfile in self._state
511 return dfile in self._state
512
512
513 def __getitem__(self, dfile):
513 def __getitem__(self, dfile):
514 return self._state[dfile][0]
514 return self._state[dfile][0]
515
515
516 def __iter__(self):
516 def __iter__(self):
517 return iter(sorted(self._state))
517 return iter(sorted(self._state))
518
518
519 def files(self):
519 def files(self):
520 return self._state.keys()
520 return self._state.keys()
521
521
522 def mark(self, dfile, state):
522 def mark(self, dfile, state):
523 self._state[dfile][0] = state
523 self._state[dfile][0] = state
524 self._dirty = True
524 self._dirty = True
525
525
526 def mdstate(self):
526 def mdstate(self):
527 return self._mdstate
527 return self._mdstate
528
528
529 def unresolved(self):
529 def unresolved(self):
530 """Obtain the paths of unresolved files."""
530 """Obtain the paths of unresolved files."""
531
531
532 for f, entry in self._state.iteritems():
532 for f, entry in self._state.iteritems():
533 if entry[0] in (MERGE_RECORD_UNRESOLVED,
533 if entry[0] in (MERGE_RECORD_UNRESOLVED,
534 MERGE_RECORD_UNRESOLVED_PATH):
534 MERGE_RECORD_UNRESOLVED_PATH):
535 yield f
535 yield f
536
536
537 def driverresolved(self):
537 def driverresolved(self):
538 """Obtain the paths of driver-resolved files."""
538 """Obtain the paths of driver-resolved files."""
539
539
540 for f, entry in self._state.items():
540 for f, entry in self._state.items():
541 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
541 if entry[0] == MERGE_RECORD_DRIVER_RESOLVED:
542 yield f
542 yield f
543
543
544 def extras(self, filename):
544 def extras(self, filename):
545 return self._stateextras.setdefault(filename, {})
545 return self._stateextras.setdefault(filename, {})
546
546
547 def _resolve(self, preresolve, dfile, wctx):
547 def _resolve(self, preresolve, dfile, wctx):
548 """rerun merge process for file path `dfile`"""
548 """rerun merge process for file path `dfile`"""
549 if self[dfile] in (MERGE_RECORD_RESOLVED,
549 if self[dfile] in (MERGE_RECORD_RESOLVED,
550 MERGE_RECORD_DRIVER_RESOLVED):
550 MERGE_RECORD_DRIVER_RESOLVED):
551 return True, 0
551 return True, 0
552 stateentry = self._state[dfile]
552 stateentry = self._state[dfile]
553 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
553 state, hash, lfile, afile, anode, ofile, onode, flags = stateentry
554 octx = self._repo[self._other]
554 octx = self._repo[self._other]
555 extras = self.extras(dfile)
555 extras = self.extras(dfile)
556 anccommitnode = extras.get('ancestorlinknode')
556 anccommitnode = extras.get('ancestorlinknode')
557 if anccommitnode:
557 if anccommitnode:
558 actx = self._repo[anccommitnode]
558 actx = self._repo[anccommitnode]
559 else:
559 else:
560 actx = None
560 actx = None
561 fcd = self._filectxorabsent(hash, wctx, dfile)
561 fcd = self._filectxorabsent(hash, wctx, dfile)
562 fco = self._filectxorabsent(onode, octx, ofile)
562 fco = self._filectxorabsent(onode, octx, ofile)
563 # TODO: move this to filectxorabsent
563 # TODO: move this to filectxorabsent
564 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
564 fca = self._repo.filectx(afile, fileid=anode, changectx=actx)
565 # "premerge" x flags
565 # "premerge" x flags
566 flo = fco.flags()
566 flo = fco.flags()
567 fla = fca.flags()
567 fla = fca.flags()
568 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
568 if 'x' in flags + flo + fla and 'l' not in flags + flo + fla:
569 if fca.node() == nullid and flags != flo:
569 if fca.node() == nullid and flags != flo:
570 if preresolve:
570 if preresolve:
571 self._repo.ui.warn(
571 self._repo.ui.warn(
572 _('warning: cannot merge flags for %s '
572 _('warning: cannot merge flags for %s '
573 'without common ancestor - keeping local flags\n')
573 'without common ancestor - keeping local flags\n')
574 % afile)
574 % afile)
575 elif flags == fla:
575 elif flags == fla:
576 flags = flo
576 flags = flo
577 if preresolve:
577 if preresolve:
578 # restore local
578 # restore local
579 if hash != nullhex:
579 if hash != nullhex:
580 f = self._repo.vfs('merge/' + hash)
580 f = self._repo.vfs('merge/' + hash)
581 wctx[dfile].write(f.read(), flags)
581 wctx[dfile].write(f.read(), flags)
582 f.close()
582 f.close()
583 else:
583 else:
584 wctx[dfile].remove(ignoremissing=True)
584 wctx[dfile].remove(ignoremissing=True)
585 complete, r, deleted = filemerge.premerge(self._repo, wctx,
585 complete, r, deleted = filemerge.premerge(self._repo, wctx,
586 self._local, lfile, fcd,
586 self._local, lfile, fcd,
587 fco, fca,
587 fco, fca,
588 labels=self._labels)
588 labels=self._labels)
589 else:
589 else:
590 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
590 complete, r, deleted = filemerge.filemerge(self._repo, wctx,
591 self._local, lfile, fcd,
591 self._local, lfile, fcd,
592 fco, fca,
592 fco, fca,
593 labels=self._labels)
593 labels=self._labels)
594 if r is None:
594 if r is None:
595 # no real conflict
595 # no real conflict
596 del self._state[dfile]
596 del self._state[dfile]
597 self._stateextras.pop(dfile, None)
597 self._stateextras.pop(dfile, None)
598 self._dirty = True
598 self._dirty = True
599 elif not r:
599 elif not r:
600 self.mark(dfile, MERGE_RECORD_RESOLVED)
600 self.mark(dfile, MERGE_RECORD_RESOLVED)
601
601
602 if complete:
602 if complete:
603 action = None
603 action = None
604 if deleted:
604 if deleted:
605 if fcd.isabsent():
605 if fcd.isabsent():
606 # dc: local picked. Need to drop if present, which may
606 # dc: local picked. Need to drop if present, which may
607 # happen on re-resolves.
607 # happen on re-resolves.
608 action = ACTION_FORGET
608 action = ACTION_FORGET
609 else:
609 else:
610 # cd: remote picked (or otherwise deleted)
610 # cd: remote picked (or otherwise deleted)
611 action = ACTION_REMOVE
611 action = ACTION_REMOVE
612 else:
612 else:
613 if fcd.isabsent(): # dc: remote picked
613 if fcd.isabsent(): # dc: remote picked
614 action = ACTION_GET
614 action = ACTION_GET
615 elif fco.isabsent(): # cd: local picked
615 elif fco.isabsent(): # cd: local picked
616 if dfile in self.localctx:
616 if dfile in self.localctx:
617 action = ACTION_ADD_MODIFIED
617 action = ACTION_ADD_MODIFIED
618 else:
618 else:
619 action = ACTION_ADD
619 action = ACTION_ADD
620 # else: regular merges (no action necessary)
620 # else: regular merges (no action necessary)
621 self._results[dfile] = r, action
621 self._results[dfile] = r, action
622
622
623 return complete, r
623 return complete, r
624
624
625 def _filectxorabsent(self, hexnode, ctx, f):
625 def _filectxorabsent(self, hexnode, ctx, f):
626 if hexnode == nullhex:
626 if hexnode == nullhex:
627 return filemerge.absentfilectx(ctx, f)
627 return filemerge.absentfilectx(ctx, f)
628 else:
628 else:
629 return ctx[f]
629 return ctx[f]
630
630
631 def preresolve(self, dfile, wctx):
631 def preresolve(self, dfile, wctx):
632 """run premerge process for dfile
632 """run premerge process for dfile
633
633
634 Returns whether the merge is complete, and the exit code."""
634 Returns whether the merge is complete, and the exit code."""
635 return self._resolve(True, dfile, wctx)
635 return self._resolve(True, dfile, wctx)
636
636
637 def resolve(self, dfile, wctx):
637 def resolve(self, dfile, wctx):
638 """run merge process (assuming premerge was run) for dfile
638 """run merge process (assuming premerge was run) for dfile
639
639
640 Returns the exit code of the merge."""
640 Returns the exit code of the merge."""
641 return self._resolve(False, dfile, wctx)[1]
641 return self._resolve(False, dfile, wctx)[1]
642
642
643 def counts(self):
643 def counts(self):
644 """return counts for updated, merged and removed files in this
644 """return counts for updated, merged and removed files in this
645 session"""
645 session"""
646 updated, merged, removed = 0, 0, 0
646 updated, merged, removed = 0, 0, 0
647 for r, action in self._results.itervalues():
647 for r, action in self._results.itervalues():
648 if r is None:
648 if r is None:
649 updated += 1
649 updated += 1
650 elif r == 0:
650 elif r == 0:
651 if action == ACTION_REMOVE:
651 if action == ACTION_REMOVE:
652 removed += 1
652 removed += 1
653 else:
653 else:
654 merged += 1
654 merged += 1
655 return updated, merged, removed
655 return updated, merged, removed
656
656
657 def unresolvedcount(self):
657 def unresolvedcount(self):
658 """get unresolved count for this merge (persistent)"""
658 """get unresolved count for this merge (persistent)"""
659 return len(list(self.unresolved()))
659 return len(list(self.unresolved()))
660
660
661 def actions(self):
661 def actions(self):
662 """return lists of actions to perform on the dirstate"""
662 """return lists of actions to perform on the dirstate"""
663 actions = {
663 actions = {
664 ACTION_REMOVE: [],
664 ACTION_REMOVE: [],
665 ACTION_FORGET: [],
665 ACTION_FORGET: [],
666 ACTION_ADD: [],
666 ACTION_ADD: [],
667 ACTION_ADD_MODIFIED: [],
667 ACTION_ADD_MODIFIED: [],
668 ACTION_GET: [],
668 ACTION_GET: [],
669 }
669 }
670 for f, (r, action) in self._results.iteritems():
670 for f, (r, action) in self._results.iteritems():
671 if action is not None:
671 if action is not None:
672 actions[action].append((f, None, "merge result"))
672 actions[action].append((f, None, "merge result"))
673 return actions
673 return actions
674
674
675 def recordactions(self):
675 def recordactions(self):
676 """record remove/add/get actions in the dirstate"""
676 """record remove/add/get actions in the dirstate"""
677 branchmerge = self._repo.dirstate.p2() != nullid
677 branchmerge = self._repo.dirstate.p2() != nullid
678 recordupdates(self._repo, self.actions(), branchmerge)
678 recordupdates(self._repo, self.actions(), branchmerge)
679
679
680 def queueremove(self, f):
680 def queueremove(self, f):
681 """queues a file to be removed from the dirstate
681 """queues a file to be removed from the dirstate
682
682
683 Meant for use by custom merge drivers."""
683 Meant for use by custom merge drivers."""
684 self._results[f] = 0, ACTION_REMOVE
684 self._results[f] = 0, ACTION_REMOVE
685
685
686 def queueadd(self, f):
686 def queueadd(self, f):
687 """queues a file to be added to the dirstate
687 """queues a file to be added to the dirstate
688
688
689 Meant for use by custom merge drivers."""
689 Meant for use by custom merge drivers."""
690 self._results[f] = 0, ACTION_ADD
690 self._results[f] = 0, ACTION_ADD
691
691
692 def queueget(self, f):
692 def queueget(self, f):
693 """queues a file to be marked modified in the dirstate
693 """queues a file to be marked modified in the dirstate
694
694
695 Meant for use by custom merge drivers."""
695 Meant for use by custom merge drivers."""
696 self._results[f] = 0, ACTION_GET
696 self._results[f] = 0, ACTION_GET
697
697
698 def _getcheckunknownconfig(repo, section, name):
698 def _getcheckunknownconfig(repo, section, name):
699 config = repo.ui.config(section, name)
699 config = repo.ui.config(section, name)
700 valid = ['abort', 'ignore', 'warn']
700 valid = ['abort', 'ignore', 'warn']
701 if config not in valid:
701 if config not in valid:
702 validstr = ', '.join(["'" + v + "'" for v in valid])
702 validstr = ', '.join(["'" + v + "'" for v in valid])
703 raise error.ConfigError(_("%s.%s not valid "
703 raise error.ConfigError(_("%s.%s not valid "
704 "('%s' is none of %s)")
704 "('%s' is none of %s)")
705 % (section, name, config, validstr))
705 % (section, name, config, validstr))
706 return config
706 return config
707
707
708 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
708 def _checkunknownfile(repo, wctx, mctx, f, f2=None):
709 if wctx.isinmemory():
709 if wctx.isinmemory():
710 # Nothing to do in IMM because nothing in the "working copy" can be an
710 # Nothing to do in IMM because nothing in the "working copy" can be an
711 # unknown file.
711 # unknown file.
712 #
712 #
713 # Note that we should bail out here, not in ``_checkunknownfiles()``,
713 # Note that we should bail out here, not in ``_checkunknownfiles()``,
714 # because that function does other useful work.
714 # because that function does other useful work.
715 return False
715 return False
716
716
717 if f2 is None:
717 if f2 is None:
718 f2 = f
718 f2 = f
719 return (repo.wvfs.audit.check(f)
719 return (repo.wvfs.audit.check(f)
720 and repo.wvfs.isfileorlink(f)
720 and repo.wvfs.isfileorlink(f)
721 and repo.dirstate.normalize(f) not in repo.dirstate
721 and repo.dirstate.normalize(f) not in repo.dirstate
722 and mctx[f2].cmp(wctx[f]))
722 and mctx[f2].cmp(wctx[f]))
723
723
724 class _unknowndirschecker(object):
724 class _unknowndirschecker(object):
725 """
725 """
726 Look for any unknown files or directories that may have a path conflict
726 Look for any unknown files or directories that may have a path conflict
727 with a file. If any path prefix of the file exists as a file or link,
727 with a file. If any path prefix of the file exists as a file or link,
728 then it conflicts. If the file itself is a directory that contains any
728 then it conflicts. If the file itself is a directory that contains any
729 file that is not tracked, then it conflicts.
729 file that is not tracked, then it conflicts.
730
730
731 Returns the shortest path at which a conflict occurs, or None if there is
731 Returns the shortest path at which a conflict occurs, or None if there is
732 no conflict.
732 no conflict.
733 """
733 """
734 def __init__(self):
734 def __init__(self):
735 # A set of paths known to be good. This prevents repeated checking of
735 # A set of paths known to be good. This prevents repeated checking of
736 # dirs. It will be updated with any new dirs that are checked and found
736 # dirs. It will be updated with any new dirs that are checked and found
737 # to be safe.
737 # to be safe.
738 self._unknowndircache = set()
738 self._unknowndircache = set()
739
739
740 # A set of paths that are known to be absent. This prevents repeated
740 # A set of paths that are known to be absent. This prevents repeated
741 # checking of subdirectories that are known not to exist. It will be
741 # checking of subdirectories that are known not to exist. It will be
742 # updated with any new dirs that are checked and found to be absent.
742 # updated with any new dirs that are checked and found to be absent.
743 self._missingdircache = set()
743 self._missingdircache = set()
744
744
745 def __call__(self, repo, wctx, f):
745 def __call__(self, repo, wctx, f):
746 if wctx.isinmemory():
746 if wctx.isinmemory():
747 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
747 # Nothing to do in IMM for the same reason as ``_checkunknownfile``.
748 return False
748 return False
749
749
750 # Check for path prefixes that exist as unknown files.
750 # Check for path prefixes that exist as unknown files.
751 for p in reversed(list(util.finddirs(f))):
751 for p in reversed(list(util.finddirs(f))):
752 if p in self._missingdircache:
752 if p in self._missingdircache:
753 return
753 return
754 if p in self._unknowndircache:
754 if p in self._unknowndircache:
755 continue
755 continue
756 if repo.wvfs.audit.check(p):
756 if repo.wvfs.audit.check(p):
757 if (repo.wvfs.isfileorlink(p)
757 if (repo.wvfs.isfileorlink(p)
758 and repo.dirstate.normalize(p) not in repo.dirstate):
758 and repo.dirstate.normalize(p) not in repo.dirstate):
759 return p
759 return p
760 if not repo.wvfs.lexists(p):
760 if not repo.wvfs.lexists(p):
761 self._missingdircache.add(p)
761 self._missingdircache.add(p)
762 return
762 return
763 self._unknowndircache.add(p)
763 self._unknowndircache.add(p)
764
764
765 # Check if the file conflicts with a directory containing unknown files.
765 # Check if the file conflicts with a directory containing unknown files.
766 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
766 if repo.wvfs.audit.check(f) and repo.wvfs.isdir(f):
767 # Does the directory contain any files that are not in the dirstate?
767 # Does the directory contain any files that are not in the dirstate?
768 for p, dirs, files in repo.wvfs.walk(f):
768 for p, dirs, files in repo.wvfs.walk(f):
769 for fn in files:
769 for fn in files:
770 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
770 relf = util.pconvert(repo.wvfs.reljoin(p, fn))
771 relf = repo.dirstate.normalize(relf, isknown=True)
771 relf = repo.dirstate.normalize(relf, isknown=True)
772 if relf not in repo.dirstate:
772 if relf not in repo.dirstate:
773 return f
773 return f
774 return None
774 return None
775
775
776 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
776 def _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce):
777 """
777 """
778 Considers any actions that care about the presence of conflicting unknown
778 Considers any actions that care about the presence of conflicting unknown
779 files. For some actions, the result is to abort; for others, it is to
779 files. For some actions, the result is to abort; for others, it is to
780 choose a different action.
780 choose a different action.
781 """
781 """
782 fileconflicts = set()
782 fileconflicts = set()
783 pathconflicts = set()
783 pathconflicts = set()
784 warnconflicts = set()
784 warnconflicts = set()
785 abortconflicts = set()
785 abortconflicts = set()
786 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
786 unknownconfig = _getcheckunknownconfig(repo, 'merge', 'checkunknown')
787 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
787 ignoredconfig = _getcheckunknownconfig(repo, 'merge', 'checkignored')
788 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
788 pathconfig = repo.ui.configbool('experimental', 'merge.checkpathconflicts')
789 if not force:
789 if not force:
790 def collectconflicts(conflicts, config):
790 def collectconflicts(conflicts, config):
791 if config == 'abort':
791 if config == 'abort':
792 abortconflicts.update(conflicts)
792 abortconflicts.update(conflicts)
793 elif config == 'warn':
793 elif config == 'warn':
794 warnconflicts.update(conflicts)
794 warnconflicts.update(conflicts)
795
795
796 checkunknowndirs = _unknowndirschecker()
796 checkunknowndirs = _unknowndirschecker()
797 for f, (m, args, msg) in actions.iteritems():
797 for f, (m, args, msg) in actions.iteritems():
798 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
798 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED):
799 if _checkunknownfile(repo, wctx, mctx, f):
799 if _checkunknownfile(repo, wctx, mctx, f):
800 fileconflicts.add(f)
800 fileconflicts.add(f)
801 elif pathconfig and f not in wctx:
801 elif pathconfig and f not in wctx:
802 path = checkunknowndirs(repo, wctx, f)
802 path = checkunknowndirs(repo, wctx, f)
803 if path is not None:
803 if path is not None:
804 pathconflicts.add(path)
804 pathconflicts.add(path)
805 elif m == ACTION_LOCAL_DIR_RENAME_GET:
805 elif m == ACTION_LOCAL_DIR_RENAME_GET:
806 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
806 if _checkunknownfile(repo, wctx, mctx, f, args[0]):
807 fileconflicts.add(f)
807 fileconflicts.add(f)
808
808
809 allconflicts = fileconflicts | pathconflicts
809 allconflicts = fileconflicts | pathconflicts
810 ignoredconflicts = set([c for c in allconflicts
810 ignoredconflicts = set([c for c in allconflicts
811 if repo.dirstate._ignore(c)])
811 if repo.dirstate._ignore(c)])
812 unknownconflicts = allconflicts - ignoredconflicts
812 unknownconflicts = allconflicts - ignoredconflicts
813 collectconflicts(ignoredconflicts, ignoredconfig)
813 collectconflicts(ignoredconflicts, ignoredconfig)
814 collectconflicts(unknownconflicts, unknownconfig)
814 collectconflicts(unknownconflicts, unknownconfig)
815 else:
815 else:
816 for f, (m, args, msg) in actions.iteritems():
816 for f, (m, args, msg) in actions.iteritems():
817 if m == ACTION_CREATED_MERGE:
817 if m == ACTION_CREATED_MERGE:
818 fl2, anc = args
818 fl2, anc = args
819 different = _checkunknownfile(repo, wctx, mctx, f)
819 different = _checkunknownfile(repo, wctx, mctx, f)
820 if repo.dirstate._ignore(f):
820 if repo.dirstate._ignore(f):
821 config = ignoredconfig
821 config = ignoredconfig
822 else:
822 else:
823 config = unknownconfig
823 config = unknownconfig
824
824
825 # The behavior when force is True is described by this table:
825 # The behavior when force is True is described by this table:
826 # config different mergeforce | action backup
826 # config different mergeforce | action backup
827 # * n * | get n
827 # * n * | get n
828 # * y y | merge -
828 # * y y | merge -
829 # abort y n | merge - (1)
829 # abort y n | merge - (1)
830 # warn y n | warn + get y
830 # warn y n | warn + get y
831 # ignore y n | get y
831 # ignore y n | get y
832 #
832 #
833 # (1) this is probably the wrong behavior here -- we should
833 # (1) this is probably the wrong behavior here -- we should
834 # probably abort, but some actions like rebases currently
834 # probably abort, but some actions like rebases currently
835 # don't like an abort happening in the middle of
835 # don't like an abort happening in the middle of
836 # merge.update.
836 # merge.update.
837 if not different:
837 if not different:
838 actions[f] = (ACTION_GET, (fl2, False), 'remote created')
838 actions[f] = (ACTION_GET, (fl2, False), 'remote created')
839 elif mergeforce or config == 'abort':
839 elif mergeforce or config == 'abort':
840 actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
840 actions[f] = (ACTION_MERGE, (f, f, None, False, anc),
841 'remote differs from untracked local')
841 'remote differs from untracked local')
842 elif config == 'abort':
842 elif config == 'abort':
843 abortconflicts.add(f)
843 abortconflicts.add(f)
844 else:
844 else:
845 if config == 'warn':
845 if config == 'warn':
846 warnconflicts.add(f)
846 warnconflicts.add(f)
847 actions[f] = (ACTION_GET, (fl2, True), 'remote created')
847 actions[f] = (ACTION_GET, (fl2, True), 'remote created')
848
848
849 for f in sorted(abortconflicts):
849 for f in sorted(abortconflicts):
850 warn = repo.ui.warn
850 warn = repo.ui.warn
851 if f in pathconflicts:
851 if f in pathconflicts:
852 if repo.wvfs.isfileorlink(f):
852 if repo.wvfs.isfileorlink(f):
853 warn(_("%s: untracked file conflicts with directory\n") % f)
853 warn(_("%s: untracked file conflicts with directory\n") % f)
854 else:
854 else:
855 warn(_("%s: untracked directory conflicts with file\n") % f)
855 warn(_("%s: untracked directory conflicts with file\n") % f)
856 else:
856 else:
857 warn(_("%s: untracked file differs\n") % f)
857 warn(_("%s: untracked file differs\n") % f)
858 if abortconflicts:
858 if abortconflicts:
859 raise error.Abort(_("untracked files in working directory "
859 raise error.Abort(_("untracked files in working directory "
860 "differ from files in requested revision"))
860 "differ from files in requested revision"))
861
861
862 for f in sorted(warnconflicts):
862 for f in sorted(warnconflicts):
863 if repo.wvfs.isfileorlink(f):
863 if repo.wvfs.isfileorlink(f):
864 repo.ui.warn(_("%s: replacing untracked file\n") % f)
864 repo.ui.warn(_("%s: replacing untracked file\n") % f)
865 else:
865 else:
866 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
866 repo.ui.warn(_("%s: replacing untracked files in directory\n") % f)
867
867
868 for f, (m, args, msg) in actions.iteritems():
868 for f, (m, args, msg) in actions.iteritems():
869 if m == ACTION_CREATED:
869 if m == ACTION_CREATED:
870 backup = (f in fileconflicts or f in pathconflicts or
870 backup = (f in fileconflicts or f in pathconflicts or
871 any(p in pathconflicts for p in util.finddirs(f)))
871 any(p in pathconflicts for p in util.finddirs(f)))
872 flags, = args
872 flags, = args
873 actions[f] = (ACTION_GET, (flags, backup), msg)
873 actions[f] = (ACTION_GET, (flags, backup), msg)
874
874
875 def _forgetremoved(wctx, mctx, branchmerge):
875 def _forgetremoved(wctx, mctx, branchmerge):
876 """
876 """
877 Forget removed files
877 Forget removed files
878
878
879 If we're jumping between revisions (as opposed to merging), and if
879 If we're jumping between revisions (as opposed to merging), and if
880 neither the working directory nor the target rev has the file,
880 neither the working directory nor the target rev has the file,
881 then we need to remove it from the dirstate, to prevent the
881 then we need to remove it from the dirstate, to prevent the
882 dirstate from listing the file when it is no longer in the
882 dirstate from listing the file when it is no longer in the
883 manifest.
883 manifest.
884
884
885 If we're merging, and the other revision has removed a file
885 If we're merging, and the other revision has removed a file
886 that is not present in the working directory, we need to mark it
886 that is not present in the working directory, we need to mark it
887 as removed.
887 as removed.
888 """
888 """
889
889
890 actions = {}
890 actions = {}
891 m = ACTION_FORGET
891 m = ACTION_FORGET
892 if branchmerge:
892 if branchmerge:
893 m = ACTION_REMOVE
893 m = ACTION_REMOVE
894 for f in wctx.deleted():
894 for f in wctx.deleted():
895 if f not in mctx:
895 if f not in mctx:
896 actions[f] = m, None, "forget deleted"
896 actions[f] = m, None, "forget deleted"
897
897
898 if not branchmerge:
898 if not branchmerge:
899 for f in wctx.removed():
899 for f in wctx.removed():
900 if f not in mctx:
900 if f not in mctx:
901 actions[f] = ACTION_FORGET, None, "forget removed"
901 actions[f] = ACTION_FORGET, None, "forget removed"
902
902
903 return actions
903 return actions
904
904
905 def _checkcollision(repo, wmf, actions):
905 def _checkcollision(repo, wmf, actions):
906 """
906 """
907 Check for case-folding collisions.
907 Check for case-folding collisions.
908 """
908 """
909
909
910 # If the repo is narrowed, filter out files outside the narrowspec.
910 # If the repo is narrowed, filter out files outside the narrowspec.
911 narrowmatch = repo.narrowmatch()
911 narrowmatch = repo.narrowmatch()
912 if not narrowmatch.always():
912 if not narrowmatch.always():
913 wmf = wmf.matches(narrowmatch)
913 wmf = wmf.matches(narrowmatch)
914 if actions:
914 if actions:
915 narrowactions = {}
915 narrowactions = {}
916 for m, actionsfortype in actions.iteritems():
916 for m, actionsfortype in actions.iteritems():
917 narrowactions[m] = []
917 narrowactions[m] = []
918 for (f, args, msg) in actionsfortype:
918 for (f, args, msg) in actionsfortype:
919 if narrowmatch(f):
919 if narrowmatch(f):
920 narrowactions[m].append((f, args, msg))
920 narrowactions[m].append((f, args, msg))
921 actions = narrowactions
921 actions = narrowactions
922
922
923 # build provisional merged manifest up
923 # build provisional merged manifest up
924 pmmf = set(wmf)
924 pmmf = set(wmf)
925
925
926 if actions:
926 if actions:
927 # KEEP and EXEC are no-op
927 # KEEP and EXEC are no-op
928 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
928 for m in (ACTION_ADD, ACTION_ADD_MODIFIED, ACTION_FORGET, ACTION_GET,
929 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
929 ACTION_CHANGED_DELETED, ACTION_DELETED_CHANGED):
930 for f, args, msg in actions[m]:
930 for f, args, msg in actions[m]:
931 pmmf.add(f)
931 pmmf.add(f)
932 for f, args, msg in actions[ACTION_REMOVE]:
932 for f, args, msg in actions[ACTION_REMOVE]:
933 pmmf.discard(f)
933 pmmf.discard(f)
934 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
934 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
935 f2, flags = args
935 f2, flags = args
936 pmmf.discard(f2)
936 pmmf.discard(f2)
937 pmmf.add(f)
937 pmmf.add(f)
938 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
938 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
939 pmmf.add(f)
939 pmmf.add(f)
940 for f, args, msg in actions[ACTION_MERGE]:
940 for f, args, msg in actions[ACTION_MERGE]:
941 f1, f2, fa, move, anc = args
941 f1, f2, fa, move, anc = args
942 if move:
942 if move:
943 pmmf.discard(f1)
943 pmmf.discard(f1)
944 pmmf.add(f)
944 pmmf.add(f)
945
945
946 # check case-folding collision in provisional merged manifest
946 # check case-folding collision in provisional merged manifest
947 foldmap = {}
947 foldmap = {}
948 for f in pmmf:
948 for f in pmmf:
949 fold = util.normcase(f)
949 fold = util.normcase(f)
950 if fold in foldmap:
950 if fold in foldmap:
951 raise error.Abort(_("case-folding collision between %s and %s")
951 raise error.Abort(_("case-folding collision between %s and %s")
952 % (f, foldmap[fold]))
952 % (f, foldmap[fold]))
953 foldmap[fold] = f
953 foldmap[fold] = f
954
954
955 # check case-folding of directories
955 # check case-folding of directories
956 foldprefix = unfoldprefix = lastfull = ''
956 foldprefix = unfoldprefix = lastfull = ''
957 for fold, f in sorted(foldmap.items()):
957 for fold, f in sorted(foldmap.items()):
958 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
958 if fold.startswith(foldprefix) and not f.startswith(unfoldprefix):
959 # the folded prefix matches but actual casing is different
959 # the folded prefix matches but actual casing is different
960 raise error.Abort(_("case-folding collision between "
960 raise error.Abort(_("case-folding collision between "
961 "%s and directory of %s") % (lastfull, f))
961 "%s and directory of %s") % (lastfull, f))
962 foldprefix = fold + '/'
962 foldprefix = fold + '/'
963 unfoldprefix = f + '/'
963 unfoldprefix = f + '/'
964 lastfull = f
964 lastfull = f
965
965
966 def driverpreprocess(repo, ms, wctx, labels=None):
966 def driverpreprocess(repo, ms, wctx, labels=None):
967 """run the preprocess step of the merge driver, if any
967 """run the preprocess step of the merge driver, if any
968
968
969 This is currently not implemented -- it's an extension point."""
969 This is currently not implemented -- it's an extension point."""
970 return True
970 return True
971
971
972 def driverconclude(repo, ms, wctx, labels=None):
972 def driverconclude(repo, ms, wctx, labels=None):
973 """run the conclude step of the merge driver, if any
973 """run the conclude step of the merge driver, if any
974
974
975 This is currently not implemented -- it's an extension point."""
975 This is currently not implemented -- it's an extension point."""
976 return True
976 return True
977
977
978 def _filesindirs(repo, manifest, dirs):
978 def _filesindirs(repo, manifest, dirs):
979 """
979 """
980 Generator that yields pairs of all the files in the manifest that are found
980 Generator that yields pairs of all the files in the manifest that are found
981 inside the directories listed in dirs, and which directory they are found
981 inside the directories listed in dirs, and which directory they are found
982 in.
982 in.
983 """
983 """
984 for f in manifest:
984 for f in manifest:
985 for p in util.finddirs(f):
985 for p in util.finddirs(f):
986 if p in dirs:
986 if p in dirs:
987 yield f, p
987 yield f, p
988 break
988 break
989
989
990 def checkpathconflicts(repo, wctx, mctx, actions):
990 def checkpathconflicts(repo, wctx, mctx, actions):
991 """
991 """
992 Check if any actions introduce path conflicts in the repository, updating
992 Check if any actions introduce path conflicts in the repository, updating
993 actions to record or handle the path conflict accordingly.
993 actions to record or handle the path conflict accordingly.
994 """
994 """
995 mf = wctx.manifest()
995 mf = wctx.manifest()
996
996
997 # The set of local files that conflict with a remote directory.
997 # The set of local files that conflict with a remote directory.
998 localconflicts = set()
998 localconflicts = set()
999
999
1000 # The set of directories that conflict with a remote file, and so may cause
1000 # The set of directories that conflict with a remote file, and so may cause
1001 # conflicts if they still contain any files after the merge.
1001 # conflicts if they still contain any files after the merge.
1002 remoteconflicts = set()
1002 remoteconflicts = set()
1003
1003
1004 # The set of directories that appear as both a file and a directory in the
1004 # The set of directories that appear as both a file and a directory in the
1005 # remote manifest. These indicate an invalid remote manifest, which
1005 # remote manifest. These indicate an invalid remote manifest, which
1006 # can't be updated to cleanly.
1006 # can't be updated to cleanly.
1007 invalidconflicts = set()
1007 invalidconflicts = set()
1008
1008
1009 # The set of directories that contain files that are being created.
1009 # The set of directories that contain files that are being created.
1010 createdfiledirs = set()
1010 createdfiledirs = set()
1011
1011
1012 # The set of files deleted by all the actions.
1012 # The set of files deleted by all the actions.
1013 deletedfiles = set()
1013 deletedfiles = set()
1014
1014
1015 for f, (m, args, msg) in actions.items():
1015 for f, (m, args, msg) in actions.items():
1016 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
1016 if m in (ACTION_CREATED, ACTION_DELETED_CHANGED, ACTION_MERGE,
1017 ACTION_CREATED_MERGE):
1017 ACTION_CREATED_MERGE):
1018 # This action may create a new local file.
1018 # This action may create a new local file.
1019 createdfiledirs.update(util.finddirs(f))
1019 createdfiledirs.update(util.finddirs(f))
1020 if mf.hasdir(f):
1020 if mf.hasdir(f):
1021 # The file aliases a local directory. This might be ok if all
1021 # The file aliases a local directory. This might be ok if all
1022 # the files in the local directory are being deleted. This
1022 # the files in the local directory are being deleted. This
1023 # will be checked once we know what all the deleted files are.
1023 # will be checked once we know what all the deleted files are.
1024 remoteconflicts.add(f)
1024 remoteconflicts.add(f)
1025 # Track the names of all deleted files.
1025 # Track the names of all deleted files.
1026 if m == ACTION_REMOVE:
1026 if m == ACTION_REMOVE:
1027 deletedfiles.add(f)
1027 deletedfiles.add(f)
1028 if m == ACTION_MERGE:
1028 if m == ACTION_MERGE:
1029 f1, f2, fa, move, anc = args
1029 f1, f2, fa, move, anc = args
1030 if move:
1030 if move:
1031 deletedfiles.add(f1)
1031 deletedfiles.add(f1)
1032 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1032 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1033 f2, flags = args
1033 f2, flags = args
1034 deletedfiles.add(f2)
1034 deletedfiles.add(f2)
1035
1035
1036 # Check all directories that contain created files for path conflicts.
1036 # Check all directories that contain created files for path conflicts.
1037 for p in createdfiledirs:
1037 for p in createdfiledirs:
1038 if p in mf:
1038 if p in mf:
1039 if p in mctx:
1039 if p in mctx:
1040 # A file is in a directory which aliases both a local
1040 # A file is in a directory which aliases both a local
1041 # and a remote file. This is an internal inconsistency
1041 # and a remote file. This is an internal inconsistency
1042 # within the remote manifest.
1042 # within the remote manifest.
1043 invalidconflicts.add(p)
1043 invalidconflicts.add(p)
1044 else:
1044 else:
1045 # A file is in a directory which aliases a local file.
1045 # A file is in a directory which aliases a local file.
1046 # We will need to rename the local file.
1046 # We will need to rename the local file.
1047 localconflicts.add(p)
1047 localconflicts.add(p)
1048 if p in actions and actions[p][0] in (ACTION_CREATED,
1048 if p in actions and actions[p][0] in (ACTION_CREATED,
1049 ACTION_DELETED_CHANGED,
1049 ACTION_DELETED_CHANGED,
1050 ACTION_MERGE,
1050 ACTION_MERGE,
1051 ACTION_CREATED_MERGE):
1051 ACTION_CREATED_MERGE):
1052 # The file is in a directory which aliases a remote file.
1052 # The file is in a directory which aliases a remote file.
1053 # This is an internal inconsistency within the remote
1053 # This is an internal inconsistency within the remote
1054 # manifest.
1054 # manifest.
1055 invalidconflicts.add(p)
1055 invalidconflicts.add(p)
1056
1056
1057 # Rename all local conflicting files that have not been deleted.
1057 # Rename all local conflicting files that have not been deleted.
1058 for p in localconflicts:
1058 for p in localconflicts:
1059 if p not in deletedfiles:
1059 if p not in deletedfiles:
1060 ctxname = bytes(wctx).rstrip('+')
1060 ctxname = bytes(wctx).rstrip('+')
1061 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1061 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1062 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
1062 actions[pnew] = (ACTION_PATH_CONFLICT_RESOLVE, (p,),
1063 'local path conflict')
1063 'local path conflict')
1064 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
1064 actions[p] = (ACTION_PATH_CONFLICT, (pnew, 'l'),
1065 'path conflict')
1065 'path conflict')
1066
1066
1067 if remoteconflicts:
1067 if remoteconflicts:
1068 # Check if all files in the conflicting directories have been removed.
1068 # Check if all files in the conflicting directories have been removed.
1069 ctxname = bytes(mctx).rstrip('+')
1069 ctxname = bytes(mctx).rstrip('+')
1070 for f, p in _filesindirs(repo, mf, remoteconflicts):
1070 for f, p in _filesindirs(repo, mf, remoteconflicts):
1071 if f not in deletedfiles:
1071 if f not in deletedfiles:
1072 m, args, msg = actions[p]
1072 m, args, msg = actions[p]
1073 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1073 pnew = util.safename(p, ctxname, wctx, set(actions.keys()))
1074 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1074 if m in (ACTION_DELETED_CHANGED, ACTION_MERGE):
1075 # Action was merge, just update target.
1075 # Action was merge, just update target.
1076 actions[pnew] = (m, args, msg)
1076 actions[pnew] = (m, args, msg)
1077 else:
1077 else:
1078 # Action was create, change to renamed get action.
1078 # Action was create, change to renamed get action.
1079 fl = args[0]
1079 fl = args[0]
1080 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
1080 actions[pnew] = (ACTION_LOCAL_DIR_RENAME_GET, (p, fl),
1081 'remote path conflict')
1081 'remote path conflict')
1082 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
1082 actions[p] = (ACTION_PATH_CONFLICT, (pnew, ACTION_REMOVE),
1083 'path conflict')
1083 'path conflict')
1084 remoteconflicts.remove(p)
1084 remoteconflicts.remove(p)
1085 break
1085 break
1086
1086
1087 if invalidconflicts:
1087 if invalidconflicts:
1088 for p in invalidconflicts:
1088 for p in invalidconflicts:
1089 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
1089 repo.ui.warn(_("%s: is both a file and a directory\n") % p)
1090 raise error.Abort(_("destination manifest contains path conflicts"))
1090 raise error.Abort(_("destination manifest contains path conflicts"))
1091
1091
1092 def _filternarrowactions(narrowmatch, branchmerge, actions):
1092 def _filternarrowactions(narrowmatch, branchmerge, actions):
1093 """
1093 """
1094 Filters out actions that can ignored because the repo is narrowed.
1094 Filters out actions that can ignored because the repo is narrowed.
1095
1095
1096 Raise an exception if the merge cannot be completed because the repo is
1096 Raise an exception if the merge cannot be completed because the repo is
1097 narrowed.
1097 narrowed.
1098 """
1098 """
1099 nooptypes = set(['k']) # TODO: handle with nonconflicttypes
1099 nooptypes = set(['k']) # TODO: handle with nonconflicttypes
1100 nonconflicttypes = set('a am c cm f g r e'.split())
1100 nonconflicttypes = set('a am c cm f g r e'.split())
1101 # We mutate the items in the dict during iteration, so iterate
1101 # We mutate the items in the dict during iteration, so iterate
1102 # over a copy.
1102 # over a copy.
1103 for f, action in list(actions.items()):
1103 for f, action in list(actions.items()):
1104 if narrowmatch(f):
1104 if narrowmatch(f):
1105 pass
1105 pass
1106 elif not branchmerge:
1106 elif not branchmerge:
1107 del actions[f] # just updating, ignore changes outside clone
1107 del actions[f] # just updating, ignore changes outside clone
1108 elif action[0] in nooptypes:
1108 elif action[0] in nooptypes:
1109 del actions[f] # merge does not affect file
1109 del actions[f] # merge does not affect file
1110 elif action[0] in nonconflicttypes:
1110 elif action[0] in nonconflicttypes:
1111 raise error.Abort(_('merge affects file \'%s\' outside narrow, '
1111 raise error.Abort(_('merge affects file \'%s\' outside narrow, '
1112 'which is not yet supported') % f,
1112 'which is not yet supported') % f,
1113 hint=_('merging in the other direction '
1113 hint=_('merging in the other direction '
1114 'may work'))
1114 'may work'))
1115 else:
1115 else:
1116 raise error.Abort(_('conflict in file \'%s\' is outside '
1116 raise error.Abort(_('conflict in file \'%s\' is outside '
1117 'narrow clone') % f)
1117 'narrow clone') % f)
1118
1118
1119 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
1119 def manifestmerge(repo, wctx, p2, pa, branchmerge, force, matcher,
1120 acceptremote, followcopies, forcefulldiff=False):
1120 acceptremote, followcopies, forcefulldiff=False):
1121 """
1121 """
1122 Merge wctx and p2 with ancestor pa and generate merge action list
1122 Merge wctx and p2 with ancestor pa and generate merge action list
1123
1123
1124 branchmerge and force are as passed in to update
1124 branchmerge and force are as passed in to update
1125 matcher = matcher to filter file lists
1125 matcher = matcher to filter file lists
1126 acceptremote = accept the incoming changes without prompting
1126 acceptremote = accept the incoming changes without prompting
1127 """
1127 """
1128 if matcher is not None and matcher.always():
1128 if matcher is not None and matcher.always():
1129 matcher = None
1129 matcher = None
1130
1130
1131 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1131 copy, movewithdir, diverge, renamedelete, dirmove = {}, {}, {}, {}, {}
1132
1132
1133 # manifests fetched in order are going to be faster, so prime the caches
1133 # manifests fetched in order are going to be faster, so prime the caches
1134 [x.manifest() for x in
1134 [x.manifest() for x in
1135 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
1135 sorted(wctx.parents() + [p2, pa], key=scmutil.intrev)]
1136
1136
1137 if followcopies:
1137 if followcopies:
1138 ret = copies.mergecopies(repo, wctx, p2, pa)
1138 ret = copies.mergecopies(repo, wctx, p2, pa)
1139 copy, movewithdir, diverge, renamedelete, dirmove = ret
1139 copy, movewithdir, diverge, renamedelete, dirmove = ret
1140
1140
1141 boolbm = pycompat.bytestr(bool(branchmerge))
1141 boolbm = pycompat.bytestr(bool(branchmerge))
1142 boolf = pycompat.bytestr(bool(force))
1142 boolf = pycompat.bytestr(bool(force))
1143 boolm = pycompat.bytestr(bool(matcher))
1143 boolm = pycompat.bytestr(bool(matcher))
1144 repo.ui.note(_("resolving manifests\n"))
1144 repo.ui.note(_("resolving manifests\n"))
1145 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
1145 repo.ui.debug(" branchmerge: %s, force: %s, partial: %s\n"
1146 % (boolbm, boolf, boolm))
1146 % (boolbm, boolf, boolm))
1147 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1147 repo.ui.debug(" ancestor: %s, local: %s, remote: %s\n" % (pa, wctx, p2))
1148
1148
1149 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1149 m1, m2, ma = wctx.manifest(), p2.manifest(), pa.manifest()
1150 copied = set(copy.values())
1150 copied = set(copy.values())
1151 copied.update(movewithdir.values())
1151 copied.update(movewithdir.values())
1152
1152
1153 if '.hgsubstate' in m1:
1153 if '.hgsubstate' in m1 and wctx.rev() is None:
1154 # check whether sub state is modified
1154 # Check whether sub state is modified, and overwrite the manifest
1155 # to flag the change. If wctx is a committed revision, we shouldn't
1156 # care for the dirty state of the working directory.
1155 if any(wctx.sub(s).dirty() for s in wctx.substate):
1157 if any(wctx.sub(s).dirty() for s in wctx.substate):
1156 m1['.hgsubstate'] = modifiednodeid
1158 m1['.hgsubstate'] = modifiednodeid
1157
1159
1158 # Don't use m2-vs-ma optimization if:
1160 # Don't use m2-vs-ma optimization if:
1159 # - ma is the same as m1 or m2, which we're just going to diff again later
1161 # - ma is the same as m1 or m2, which we're just going to diff again later
1160 # - The caller specifically asks for a full diff, which is useful during bid
1162 # - The caller specifically asks for a full diff, which is useful during bid
1161 # merge.
1163 # merge.
1162 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1164 if (pa not in ([wctx, p2] + wctx.parents()) and not forcefulldiff):
1163 # Identify which files are relevant to the merge, so we can limit the
1165 # Identify which files are relevant to the merge, so we can limit the
1164 # total m1-vs-m2 diff to just those files. This has significant
1166 # total m1-vs-m2 diff to just those files. This has significant
1165 # performance benefits in large repositories.
1167 # performance benefits in large repositories.
1166 relevantfiles = set(ma.diff(m2).keys())
1168 relevantfiles = set(ma.diff(m2).keys())
1167
1169
1168 # For copied and moved files, we need to add the source file too.
1170 # For copied and moved files, we need to add the source file too.
1169 for copykey, copyvalue in copy.iteritems():
1171 for copykey, copyvalue in copy.iteritems():
1170 if copyvalue in relevantfiles:
1172 if copyvalue in relevantfiles:
1171 relevantfiles.add(copykey)
1173 relevantfiles.add(copykey)
1172 for movedirkey in movewithdir:
1174 for movedirkey in movewithdir:
1173 relevantfiles.add(movedirkey)
1175 relevantfiles.add(movedirkey)
1174 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1176 filesmatcher = scmutil.matchfiles(repo, relevantfiles)
1175 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1177 matcher = matchmod.intersectmatchers(matcher, filesmatcher)
1176
1178
1177 diff = m1.diff(m2, match=matcher)
1179 diff = m1.diff(m2, match=matcher)
1178
1180
1179 if matcher is None:
1181 if matcher is None:
1180 matcher = matchmod.always('', '')
1182 matcher = matchmod.always('', '')
1181
1183
1182 actions = {}
1184 actions = {}
1183 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1185 for f, ((n1, fl1), (n2, fl2)) in diff.iteritems():
1184 if n1 and n2: # file exists on both local and remote side
1186 if n1 and n2: # file exists on both local and remote side
1185 if f not in ma:
1187 if f not in ma:
1186 fa = copy.get(f, None)
1188 fa = copy.get(f, None)
1187 if fa is not None:
1189 if fa is not None:
1188 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
1190 actions[f] = (ACTION_MERGE, (f, f, fa, False, pa.node()),
1189 'both renamed from %s' % fa)
1191 'both renamed from %s' % fa)
1190 else:
1192 else:
1191 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
1193 actions[f] = (ACTION_MERGE, (f, f, None, False, pa.node()),
1192 'both created')
1194 'both created')
1193 else:
1195 else:
1194 a = ma[f]
1196 a = ma[f]
1195 fla = ma.flags(f)
1197 fla = ma.flags(f)
1196 nol = 'l' not in fl1 + fl2 + fla
1198 nol = 'l' not in fl1 + fl2 + fla
1197 if n2 == a and fl2 == fla:
1199 if n2 == a and fl2 == fla:
1198 actions[f] = (ACTION_KEEP, (), 'remote unchanged')
1200 actions[f] = (ACTION_KEEP, (), 'remote unchanged')
1199 elif n1 == a and fl1 == fla: # local unchanged - use remote
1201 elif n1 == a and fl1 == fla: # local unchanged - use remote
1200 if n1 == n2: # optimization: keep local content
1202 if n1 == n2: # optimization: keep local content
1201 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1203 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1202 else:
1204 else:
1203 actions[f] = (ACTION_GET, (fl2, False),
1205 actions[f] = (ACTION_GET, (fl2, False),
1204 'remote is newer')
1206 'remote is newer')
1205 elif nol and n2 == a: # remote only changed 'x'
1207 elif nol and n2 == a: # remote only changed 'x'
1206 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1208 actions[f] = (ACTION_EXEC, (fl2,), 'update permissions')
1207 elif nol and n1 == a: # local only changed 'x'
1209 elif nol and n1 == a: # local only changed 'x'
1208 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
1210 actions[f] = (ACTION_GET, (fl1, False), 'remote is newer')
1209 else: # both changed something
1211 else: # both changed something
1210 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
1212 actions[f] = (ACTION_MERGE, (f, f, f, False, pa.node()),
1211 'versions differ')
1213 'versions differ')
1212 elif n1: # file exists only on local side
1214 elif n1: # file exists only on local side
1213 if f in copied:
1215 if f in copied:
1214 pass # we'll deal with it on m2 side
1216 pass # we'll deal with it on m2 side
1215 elif f in movewithdir: # directory rename, move local
1217 elif f in movewithdir: # directory rename, move local
1216 f2 = movewithdir[f]
1218 f2 = movewithdir[f]
1217 if f2 in m2:
1219 if f2 in m2:
1218 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
1220 actions[f2] = (ACTION_MERGE, (f, f2, None, True, pa.node()),
1219 'remote directory rename, both created')
1221 'remote directory rename, both created')
1220 else:
1222 else:
1221 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
1223 actions[f2] = (ACTION_DIR_RENAME_MOVE_LOCAL, (f, fl1),
1222 'remote directory rename - move from %s' % f)
1224 'remote directory rename - move from %s' % f)
1223 elif f in copy:
1225 elif f in copy:
1224 f2 = copy[f]
1226 f2 = copy[f]
1225 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
1227 actions[f] = (ACTION_MERGE, (f, f2, f2, False, pa.node()),
1226 'local copied/moved from %s' % f2)
1228 'local copied/moved from %s' % f2)
1227 elif f in ma: # clean, a different, no remote
1229 elif f in ma: # clean, a different, no remote
1228 if n1 != ma[f]:
1230 if n1 != ma[f]:
1229 if acceptremote:
1231 if acceptremote:
1230 actions[f] = (ACTION_REMOVE, None, 'remote delete')
1232 actions[f] = (ACTION_REMOVE, None, 'remote delete')
1231 else:
1233 else:
1232 actions[f] = (ACTION_CHANGED_DELETED,
1234 actions[f] = (ACTION_CHANGED_DELETED,
1233 (f, None, f, False, pa.node()),
1235 (f, None, f, False, pa.node()),
1234 'prompt changed/deleted')
1236 'prompt changed/deleted')
1235 elif n1 == addednodeid:
1237 elif n1 == addednodeid:
1236 # This extra 'a' is added by working copy manifest to mark
1238 # This extra 'a' is added by working copy manifest to mark
1237 # the file as locally added. We should forget it instead of
1239 # the file as locally added. We should forget it instead of
1238 # deleting it.
1240 # deleting it.
1239 actions[f] = (ACTION_FORGET, None, 'remote deleted')
1241 actions[f] = (ACTION_FORGET, None, 'remote deleted')
1240 else:
1242 else:
1241 actions[f] = (ACTION_REMOVE, None, 'other deleted')
1243 actions[f] = (ACTION_REMOVE, None, 'other deleted')
1242 elif n2: # file exists only on remote side
1244 elif n2: # file exists only on remote side
1243 if f in copied:
1245 if f in copied:
1244 pass # we'll deal with it on m1 side
1246 pass # we'll deal with it on m1 side
1245 elif f in movewithdir:
1247 elif f in movewithdir:
1246 f2 = movewithdir[f]
1248 f2 = movewithdir[f]
1247 if f2 in m1:
1249 if f2 in m1:
1248 actions[f2] = (ACTION_MERGE,
1250 actions[f2] = (ACTION_MERGE,
1249 (f2, f, None, False, pa.node()),
1251 (f2, f, None, False, pa.node()),
1250 'local directory rename, both created')
1252 'local directory rename, both created')
1251 else:
1253 else:
1252 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
1254 actions[f2] = (ACTION_LOCAL_DIR_RENAME_GET, (f, fl2),
1253 'local directory rename - get from %s' % f)
1255 'local directory rename - get from %s' % f)
1254 elif f in copy:
1256 elif f in copy:
1255 f2 = copy[f]
1257 f2 = copy[f]
1256 if f2 in m2:
1258 if f2 in m2:
1257 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
1259 actions[f] = (ACTION_MERGE, (f2, f, f2, False, pa.node()),
1258 'remote copied from %s' % f2)
1260 'remote copied from %s' % f2)
1259 else:
1261 else:
1260 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
1262 actions[f] = (ACTION_MERGE, (f2, f, f2, True, pa.node()),
1261 'remote moved from %s' % f2)
1263 'remote moved from %s' % f2)
1262 elif f not in ma:
1264 elif f not in ma:
1263 # local unknown, remote created: the logic is described by the
1265 # local unknown, remote created: the logic is described by the
1264 # following table:
1266 # following table:
1265 #
1267 #
1266 # force branchmerge different | action
1268 # force branchmerge different | action
1267 # n * * | create
1269 # n * * | create
1268 # y n * | create
1270 # y n * | create
1269 # y y n | create
1271 # y y n | create
1270 # y y y | merge
1272 # y y y | merge
1271 #
1273 #
1272 # Checking whether the files are different is expensive, so we
1274 # Checking whether the files are different is expensive, so we
1273 # don't do that when we can avoid it.
1275 # don't do that when we can avoid it.
1274 if not force:
1276 if not force:
1275 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1277 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1276 elif not branchmerge:
1278 elif not branchmerge:
1277 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1279 actions[f] = (ACTION_CREATED, (fl2,), 'remote created')
1278 else:
1280 else:
1279 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
1281 actions[f] = (ACTION_CREATED_MERGE, (fl2, pa.node()),
1280 'remote created, get or merge')
1282 'remote created, get or merge')
1281 elif n2 != ma[f]:
1283 elif n2 != ma[f]:
1282 df = None
1284 df = None
1283 for d in dirmove:
1285 for d in dirmove:
1284 if f.startswith(d):
1286 if f.startswith(d):
1285 # new file added in a directory that was moved
1287 # new file added in a directory that was moved
1286 df = dirmove[d] + f[len(d):]
1288 df = dirmove[d] + f[len(d):]
1287 break
1289 break
1288 if df is not None and df in m1:
1290 if df is not None and df in m1:
1289 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
1291 actions[df] = (ACTION_MERGE, (df, f, f, False, pa.node()),
1290 'local directory rename - respect move '
1292 'local directory rename - respect move '
1291 'from %s' % f)
1293 'from %s' % f)
1292 elif acceptremote:
1294 elif acceptremote:
1293 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
1295 actions[f] = (ACTION_CREATED, (fl2,), 'remote recreating')
1294 else:
1296 else:
1295 actions[f] = (ACTION_DELETED_CHANGED,
1297 actions[f] = (ACTION_DELETED_CHANGED,
1296 (None, f, f, False, pa.node()),
1298 (None, f, f, False, pa.node()),
1297 'prompt deleted/changed')
1299 'prompt deleted/changed')
1298
1300
1299 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1301 if repo.ui.configbool('experimental', 'merge.checkpathconflicts'):
1300 # If we are merging, look for path conflicts.
1302 # If we are merging, look for path conflicts.
1301 checkpathconflicts(repo, wctx, p2, actions)
1303 checkpathconflicts(repo, wctx, p2, actions)
1302
1304
1303 narrowmatch = repo.narrowmatch()
1305 narrowmatch = repo.narrowmatch()
1304 if not narrowmatch.always():
1306 if not narrowmatch.always():
1305 # Updates "actions" in place
1307 # Updates "actions" in place
1306 _filternarrowactions(narrowmatch, branchmerge, actions)
1308 _filternarrowactions(narrowmatch, branchmerge, actions)
1307
1309
1308 return actions, diverge, renamedelete
1310 return actions, diverge, renamedelete
1309
1311
1310 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1312 def _resolvetrivial(repo, wctx, mctx, ancestor, actions):
1311 """Resolves false conflicts where the nodeid changed but the content
1313 """Resolves false conflicts where the nodeid changed but the content
1312 remained the same."""
1314 remained the same."""
1313 # We force a copy of actions.items() because we're going to mutate
1315 # We force a copy of actions.items() because we're going to mutate
1314 # actions as we resolve trivial conflicts.
1316 # actions as we resolve trivial conflicts.
1315 for f, (m, args, msg) in list(actions.items()):
1317 for f, (m, args, msg) in list(actions.items()):
1316 if (m == ACTION_CHANGED_DELETED and f in ancestor
1318 if (m == ACTION_CHANGED_DELETED and f in ancestor
1317 and not wctx[f].cmp(ancestor[f])):
1319 and not wctx[f].cmp(ancestor[f])):
1318 # local did change but ended up with same content
1320 # local did change but ended up with same content
1319 actions[f] = ACTION_REMOVE, None, 'prompt same'
1321 actions[f] = ACTION_REMOVE, None, 'prompt same'
1320 elif (m == ACTION_DELETED_CHANGED and f in ancestor
1322 elif (m == ACTION_DELETED_CHANGED and f in ancestor
1321 and not mctx[f].cmp(ancestor[f])):
1323 and not mctx[f].cmp(ancestor[f])):
1322 # remote did change but ended up with same content
1324 # remote did change but ended up with same content
1323 del actions[f] # don't get = keep local deleted
1325 del actions[f] # don't get = keep local deleted
1324
1326
1325 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1327 def calculateupdates(repo, wctx, mctx, ancestors, branchmerge, force,
1326 acceptremote, followcopies, matcher=None,
1328 acceptremote, followcopies, matcher=None,
1327 mergeforce=False):
1329 mergeforce=False):
1328 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1330 """Calculate the actions needed to merge mctx into wctx using ancestors"""
1329 # Avoid cycle.
1331 # Avoid cycle.
1330 from . import sparse
1332 from . import sparse
1331
1333
1332 if len(ancestors) == 1: # default
1334 if len(ancestors) == 1: # default
1333 actions, diverge, renamedelete = manifestmerge(
1335 actions, diverge, renamedelete = manifestmerge(
1334 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1336 repo, wctx, mctx, ancestors[0], branchmerge, force, matcher,
1335 acceptremote, followcopies)
1337 acceptremote, followcopies)
1336 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1338 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1337
1339
1338 else: # only when merge.preferancestor=* - the default
1340 else: # only when merge.preferancestor=* - the default
1339 repo.ui.note(
1341 repo.ui.note(
1340 _("note: merging %s and %s using bids from ancestors %s\n") %
1342 _("note: merging %s and %s using bids from ancestors %s\n") %
1341 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1343 (wctx, mctx, _(' and ').join(pycompat.bytestr(anc)
1342 for anc in ancestors)))
1344 for anc in ancestors)))
1343
1345
1344 # Call for bids
1346 # Call for bids
1345 fbids = {} # mapping filename to bids (action method to list af actions)
1347 fbids = {} # mapping filename to bids (action method to list af actions)
1346 diverge, renamedelete = None, None
1348 diverge, renamedelete = None, None
1347 for ancestor in ancestors:
1349 for ancestor in ancestors:
1348 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1350 repo.ui.note(_('\ncalculating bids for ancestor %s\n') % ancestor)
1349 actions, diverge1, renamedelete1 = manifestmerge(
1351 actions, diverge1, renamedelete1 = manifestmerge(
1350 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1352 repo, wctx, mctx, ancestor, branchmerge, force, matcher,
1351 acceptremote, followcopies, forcefulldiff=True)
1353 acceptremote, followcopies, forcefulldiff=True)
1352 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1354 _checkunknownfiles(repo, wctx, mctx, force, actions, mergeforce)
1353
1355
1354 # Track the shortest set of warning on the theory that bid
1356 # Track the shortest set of warning on the theory that bid
1355 # merge will correctly incorporate more information
1357 # merge will correctly incorporate more information
1356 if diverge is None or len(diverge1) < len(diverge):
1358 if diverge is None or len(diverge1) < len(diverge):
1357 diverge = diverge1
1359 diverge = diverge1
1358 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1360 if renamedelete is None or len(renamedelete) < len(renamedelete1):
1359 renamedelete = renamedelete1
1361 renamedelete = renamedelete1
1360
1362
1361 for f, a in sorted(actions.iteritems()):
1363 for f, a in sorted(actions.iteritems()):
1362 m, args, msg = a
1364 m, args, msg = a
1363 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1365 repo.ui.debug(' %s: %s -> %s\n' % (f, msg, m))
1364 if f in fbids:
1366 if f in fbids:
1365 d = fbids[f]
1367 d = fbids[f]
1366 if m in d:
1368 if m in d:
1367 d[m].append(a)
1369 d[m].append(a)
1368 else:
1370 else:
1369 d[m] = [a]
1371 d[m] = [a]
1370 else:
1372 else:
1371 fbids[f] = {m: [a]}
1373 fbids[f] = {m: [a]}
1372
1374
1373 # Pick the best bid for each file
1375 # Pick the best bid for each file
1374 repo.ui.note(_('\nauction for merging merge bids\n'))
1376 repo.ui.note(_('\nauction for merging merge bids\n'))
1375 actions = {}
1377 actions = {}
1376 dms = [] # filenames that have dm actions
1378 dms = [] # filenames that have dm actions
1377 for f, bids in sorted(fbids.items()):
1379 for f, bids in sorted(fbids.items()):
1378 # bids is a mapping from action method to list af actions
1380 # bids is a mapping from action method to list af actions
1379 # Consensus?
1381 # Consensus?
1380 if len(bids) == 1: # all bids are the same kind of method
1382 if len(bids) == 1: # all bids are the same kind of method
1381 m, l = list(bids.items())[0]
1383 m, l = list(bids.items())[0]
1382 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1384 if all(a == l[0] for a in l[1:]): # len(bids) is > 1
1383 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1385 repo.ui.note(_(" %s: consensus for %s\n") % (f, m))
1384 actions[f] = l[0]
1386 actions[f] = l[0]
1385 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1387 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1386 dms.append(f)
1388 dms.append(f)
1387 continue
1389 continue
1388 # If keep is an option, just do it.
1390 # If keep is an option, just do it.
1389 if ACTION_KEEP in bids:
1391 if ACTION_KEEP in bids:
1390 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1392 repo.ui.note(_(" %s: picking 'keep' action\n") % f)
1391 actions[f] = bids[ACTION_KEEP][0]
1393 actions[f] = bids[ACTION_KEEP][0]
1392 continue
1394 continue
1393 # If there are gets and they all agree [how could they not?], do it.
1395 # If there are gets and they all agree [how could they not?], do it.
1394 if ACTION_GET in bids:
1396 if ACTION_GET in bids:
1395 ga0 = bids[ACTION_GET][0]
1397 ga0 = bids[ACTION_GET][0]
1396 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1398 if all(a == ga0 for a in bids[ACTION_GET][1:]):
1397 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1399 repo.ui.note(_(" %s: picking 'get' action\n") % f)
1398 actions[f] = ga0
1400 actions[f] = ga0
1399 continue
1401 continue
1400 # TODO: Consider other simple actions such as mode changes
1402 # TODO: Consider other simple actions such as mode changes
1401 # Handle inefficient democrazy.
1403 # Handle inefficient democrazy.
1402 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1404 repo.ui.note(_(' %s: multiple bids for merge action:\n') % f)
1403 for m, l in sorted(bids.items()):
1405 for m, l in sorted(bids.items()):
1404 for _f, args, msg in l:
1406 for _f, args, msg in l:
1405 repo.ui.note(' %s -> %s\n' % (msg, m))
1407 repo.ui.note(' %s -> %s\n' % (msg, m))
1406 # Pick random action. TODO: Instead, prompt user when resolving
1408 # Pick random action. TODO: Instead, prompt user when resolving
1407 m, l = list(bids.items())[0]
1409 m, l = list(bids.items())[0]
1408 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1410 repo.ui.warn(_(' %s: ambiguous merge - picked %s action\n') %
1409 (f, m))
1411 (f, m))
1410 actions[f] = l[0]
1412 actions[f] = l[0]
1411 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1413 if m == ACTION_DIR_RENAME_MOVE_LOCAL:
1412 dms.append(f)
1414 dms.append(f)
1413 continue
1415 continue
1414 # Work around 'dm' that can cause multiple actions for the same file
1416 # Work around 'dm' that can cause multiple actions for the same file
1415 for f in dms:
1417 for f in dms:
1416 dm, (f0, flags), msg = actions[f]
1418 dm, (f0, flags), msg = actions[f]
1417 assert dm == ACTION_DIR_RENAME_MOVE_LOCAL, dm
1419 assert dm == ACTION_DIR_RENAME_MOVE_LOCAL, dm
1418 if f0 in actions and actions[f0][0] == ACTION_REMOVE:
1420 if f0 in actions and actions[f0][0] == ACTION_REMOVE:
1419 # We have one bid for removing a file and another for moving it.
1421 # We have one bid for removing a file and another for moving it.
1420 # These two could be merged as first move and then delete ...
1422 # These two could be merged as first move and then delete ...
1421 # but instead drop moving and just delete.
1423 # but instead drop moving and just delete.
1422 del actions[f]
1424 del actions[f]
1423 repo.ui.note(_('end of auction\n\n'))
1425 repo.ui.note(_('end of auction\n\n'))
1424
1426
1425 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1427 _resolvetrivial(repo, wctx, mctx, ancestors[0], actions)
1426
1428
1427 if wctx.rev() is None:
1429 if wctx.rev() is None:
1428 fractions = _forgetremoved(wctx, mctx, branchmerge)
1430 fractions = _forgetremoved(wctx, mctx, branchmerge)
1429 actions.update(fractions)
1431 actions.update(fractions)
1430
1432
1431 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1433 prunedactions = sparse.filterupdatesactions(repo, wctx, mctx, branchmerge,
1432 actions)
1434 actions)
1433
1435
1434 return prunedactions, diverge, renamedelete
1436 return prunedactions, diverge, renamedelete
1435
1437
1436 def _getcwd():
1438 def _getcwd():
1437 try:
1439 try:
1438 return pycompat.getcwd()
1440 return pycompat.getcwd()
1439 except OSError as err:
1441 except OSError as err:
1440 if err.errno == errno.ENOENT:
1442 if err.errno == errno.ENOENT:
1441 return None
1443 return None
1442 raise
1444 raise
1443
1445
1444 def batchremove(repo, wctx, actions):
1446 def batchremove(repo, wctx, actions):
1445 """apply removes to the working directory
1447 """apply removes to the working directory
1446
1448
1447 yields tuples for progress updates
1449 yields tuples for progress updates
1448 """
1450 """
1449 verbose = repo.ui.verbose
1451 verbose = repo.ui.verbose
1450 cwd = _getcwd()
1452 cwd = _getcwd()
1451 i = 0
1453 i = 0
1452 for f, args, msg in actions:
1454 for f, args, msg in actions:
1453 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1455 repo.ui.debug(" %s: %s -> r\n" % (f, msg))
1454 if verbose:
1456 if verbose:
1455 repo.ui.note(_("removing %s\n") % f)
1457 repo.ui.note(_("removing %s\n") % f)
1456 wctx[f].audit()
1458 wctx[f].audit()
1457 try:
1459 try:
1458 wctx[f].remove(ignoremissing=True)
1460 wctx[f].remove(ignoremissing=True)
1459 except OSError as inst:
1461 except OSError as inst:
1460 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1462 repo.ui.warn(_("update failed to remove %s: %s!\n") %
1461 (f, inst.strerror))
1463 (f, inst.strerror))
1462 if i == 100:
1464 if i == 100:
1463 yield i, f
1465 yield i, f
1464 i = 0
1466 i = 0
1465 i += 1
1467 i += 1
1466 if i > 0:
1468 if i > 0:
1467 yield i, f
1469 yield i, f
1468
1470
1469 if cwd and not _getcwd():
1471 if cwd and not _getcwd():
1470 # cwd was removed in the course of removing files; print a helpful
1472 # cwd was removed in the course of removing files; print a helpful
1471 # warning.
1473 # warning.
1472 repo.ui.warn(_("current directory was removed\n"
1474 repo.ui.warn(_("current directory was removed\n"
1473 "(consider changing to repo root: %s)\n") % repo.root)
1475 "(consider changing to repo root: %s)\n") % repo.root)
1474
1476
1475 def batchget(repo, mctx, wctx, actions):
1477 def batchget(repo, mctx, wctx, actions):
1476 """apply gets to the working directory
1478 """apply gets to the working directory
1477
1479
1478 mctx is the context to get from
1480 mctx is the context to get from
1479
1481
1480 yields tuples for progress updates
1482 yields tuples for progress updates
1481 """
1483 """
1482 verbose = repo.ui.verbose
1484 verbose = repo.ui.verbose
1483 fctx = mctx.filectx
1485 fctx = mctx.filectx
1484 ui = repo.ui
1486 ui = repo.ui
1485 i = 0
1487 i = 0
1486 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1488 with repo.wvfs.backgroundclosing(ui, expectedcount=len(actions)):
1487 for f, (flags, backup), msg in actions:
1489 for f, (flags, backup), msg in actions:
1488 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1490 repo.ui.debug(" %s: %s -> g\n" % (f, msg))
1489 if verbose:
1491 if verbose:
1490 repo.ui.note(_("getting %s\n") % f)
1492 repo.ui.note(_("getting %s\n") % f)
1491
1493
1492 if backup:
1494 if backup:
1493 # If a file or directory exists with the same name, back that
1495 # If a file or directory exists with the same name, back that
1494 # up. Otherwise, look to see if there is a file that conflicts
1496 # up. Otherwise, look to see if there is a file that conflicts
1495 # with a directory this file is in, and if so, back that up.
1497 # with a directory this file is in, and if so, back that up.
1496 absf = repo.wjoin(f)
1498 absf = repo.wjoin(f)
1497 if not repo.wvfs.lexists(f):
1499 if not repo.wvfs.lexists(f):
1498 for p in util.finddirs(f):
1500 for p in util.finddirs(f):
1499 if repo.wvfs.isfileorlink(p):
1501 if repo.wvfs.isfileorlink(p):
1500 absf = repo.wjoin(p)
1502 absf = repo.wjoin(p)
1501 break
1503 break
1502 orig = scmutil.origpath(ui, repo, absf)
1504 orig = scmutil.origpath(ui, repo, absf)
1503 if repo.wvfs.lexists(absf):
1505 if repo.wvfs.lexists(absf):
1504 util.rename(absf, orig)
1506 util.rename(absf, orig)
1505 wctx[f].clearunknown()
1507 wctx[f].clearunknown()
1506 atomictemp = ui.configbool("experimental", "update.atomic-file")
1508 atomictemp = ui.configbool("experimental", "update.atomic-file")
1507 wctx[f].write(fctx(f).data(), flags, backgroundclose=True,
1509 wctx[f].write(fctx(f).data(), flags, backgroundclose=True,
1508 atomictemp=atomictemp)
1510 atomictemp=atomictemp)
1509 if i == 100:
1511 if i == 100:
1510 yield i, f
1512 yield i, f
1511 i = 0
1513 i = 0
1512 i += 1
1514 i += 1
1513 if i > 0:
1515 if i > 0:
1514 yield i, f
1516 yield i, f
1515
1517
1516 def _prefetchfiles(repo, ctx, actions):
1518 def _prefetchfiles(repo, ctx, actions):
1517 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1519 """Invoke ``scmutil.prefetchfiles()`` for the files relevant to the dict
1518 of merge actions. ``ctx`` is the context being merged in."""
1520 of merge actions. ``ctx`` is the context being merged in."""
1519
1521
1520 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1522 # Skipping 'a', 'am', 'f', 'r', 'dm', 'e', 'k', 'p' and 'pr', because they
1521 # don't touch the context to be merged in. 'cd' is skipped, because
1523 # don't touch the context to be merged in. 'cd' is skipped, because
1522 # changed/deleted never resolves to something from the remote side.
1524 # changed/deleted never resolves to something from the remote side.
1523 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1525 oplist = [actions[a] for a in (ACTION_GET, ACTION_DELETED_CHANGED,
1524 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1526 ACTION_LOCAL_DIR_RENAME_GET, ACTION_MERGE)]
1525 prefetch = scmutil.prefetchfiles
1527 prefetch = scmutil.prefetchfiles
1526 matchfiles = scmutil.matchfiles
1528 matchfiles = scmutil.matchfiles
1527 prefetch(repo, [ctx.rev()],
1529 prefetch(repo, [ctx.rev()],
1528 matchfiles(repo,
1530 matchfiles(repo,
1529 [f for sublist in oplist for f, args, msg in sublist]))
1531 [f for sublist in oplist for f, args, msg in sublist]))
1530
1532
1531 @attr.s(frozen=True)
1533 @attr.s(frozen=True)
1532 class updateresult(object):
1534 class updateresult(object):
1533 updatedcount = attr.ib()
1535 updatedcount = attr.ib()
1534 mergedcount = attr.ib()
1536 mergedcount = attr.ib()
1535 removedcount = attr.ib()
1537 removedcount = attr.ib()
1536 unresolvedcount = attr.ib()
1538 unresolvedcount = attr.ib()
1537
1539
1538 def isempty(self):
1540 def isempty(self):
1539 return (not self.updatedcount and not self.mergedcount
1541 return (not self.updatedcount and not self.mergedcount
1540 and not self.removedcount and not self.unresolvedcount)
1542 and not self.removedcount and not self.unresolvedcount)
1541
1543
1542 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1544 def applyupdates(repo, actions, wctx, mctx, overwrite, labels=None):
1543 """apply the merge action list to the working directory
1545 """apply the merge action list to the working directory
1544
1546
1545 wctx is the working copy context
1547 wctx is the working copy context
1546 mctx is the context to be merged into the working copy
1548 mctx is the context to be merged into the working copy
1547
1549
1548 Return a tuple of counts (updated, merged, removed, unresolved) that
1550 Return a tuple of counts (updated, merged, removed, unresolved) that
1549 describes how many files were affected by the update.
1551 describes how many files were affected by the update.
1550 """
1552 """
1551
1553
1552 _prefetchfiles(repo, mctx, actions)
1554 _prefetchfiles(repo, mctx, actions)
1553
1555
1554 updated, merged, removed = 0, 0, 0
1556 updated, merged, removed = 0, 0, 0
1555 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1557 ms = mergestate.clean(repo, wctx.p1().node(), mctx.node(), labels)
1556 moves = []
1558 moves = []
1557 for m, l in actions.items():
1559 for m, l in actions.items():
1558 l.sort()
1560 l.sort()
1559
1561
1560 # 'cd' and 'dc' actions are treated like other merge conflicts
1562 # 'cd' and 'dc' actions are treated like other merge conflicts
1561 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1563 mergeactions = sorted(actions[ACTION_CHANGED_DELETED])
1562 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1564 mergeactions.extend(sorted(actions[ACTION_DELETED_CHANGED]))
1563 mergeactions.extend(actions[ACTION_MERGE])
1565 mergeactions.extend(actions[ACTION_MERGE])
1564 for f, args, msg in mergeactions:
1566 for f, args, msg in mergeactions:
1565 f1, f2, fa, move, anc = args
1567 f1, f2, fa, move, anc = args
1566 if f == '.hgsubstate': # merged internally
1568 if f == '.hgsubstate': # merged internally
1567 continue
1569 continue
1568 if f1 is None:
1570 if f1 is None:
1569 fcl = filemerge.absentfilectx(wctx, fa)
1571 fcl = filemerge.absentfilectx(wctx, fa)
1570 else:
1572 else:
1571 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1573 repo.ui.debug(" preserving %s for resolve of %s\n" % (f1, f))
1572 fcl = wctx[f1]
1574 fcl = wctx[f1]
1573 if f2 is None:
1575 if f2 is None:
1574 fco = filemerge.absentfilectx(mctx, fa)
1576 fco = filemerge.absentfilectx(mctx, fa)
1575 else:
1577 else:
1576 fco = mctx[f2]
1578 fco = mctx[f2]
1577 actx = repo[anc]
1579 actx = repo[anc]
1578 if fa in actx:
1580 if fa in actx:
1579 fca = actx[fa]
1581 fca = actx[fa]
1580 else:
1582 else:
1581 # TODO: move to absentfilectx
1583 # TODO: move to absentfilectx
1582 fca = repo.filectx(f1, fileid=nullrev)
1584 fca = repo.filectx(f1, fileid=nullrev)
1583 ms.add(fcl, fco, fca, f)
1585 ms.add(fcl, fco, fca, f)
1584 if f1 != f and move:
1586 if f1 != f and move:
1585 moves.append(f1)
1587 moves.append(f1)
1586
1588
1587 # remove renamed files after safely stored
1589 # remove renamed files after safely stored
1588 for f in moves:
1590 for f in moves:
1589 if wctx[f].lexists():
1591 if wctx[f].lexists():
1590 repo.ui.debug("removing %s\n" % f)
1592 repo.ui.debug("removing %s\n" % f)
1591 wctx[f].audit()
1593 wctx[f].audit()
1592 wctx[f].remove()
1594 wctx[f].remove()
1593
1595
1594 numupdates = sum(len(l) for m, l in actions.items()
1596 numupdates = sum(len(l) for m, l in actions.items()
1595 if m != ACTION_KEEP)
1597 if m != ACTION_KEEP)
1596 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1598 progress = repo.ui.makeprogress(_('updating'), unit=_('files'),
1597 total=numupdates)
1599 total=numupdates)
1598
1600
1599 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1601 if [a for a in actions[ACTION_REMOVE] if a[0] == '.hgsubstate']:
1600 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1602 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1601
1603
1602 # record path conflicts
1604 # record path conflicts
1603 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1605 for f, args, msg in actions[ACTION_PATH_CONFLICT]:
1604 f1, fo = args
1606 f1, fo = args
1605 s = repo.ui.status
1607 s = repo.ui.status
1606 s(_("%s: path conflict - a file or link has the same name as a "
1608 s(_("%s: path conflict - a file or link has the same name as a "
1607 "directory\n") % f)
1609 "directory\n") % f)
1608 if fo == 'l':
1610 if fo == 'l':
1609 s(_("the local file has been renamed to %s\n") % f1)
1611 s(_("the local file has been renamed to %s\n") % f1)
1610 else:
1612 else:
1611 s(_("the remote file has been renamed to %s\n") % f1)
1613 s(_("the remote file has been renamed to %s\n") % f1)
1612 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1614 s(_("resolve manually then use 'hg resolve --mark %s'\n") % f)
1613 ms.addpath(f, f1, fo)
1615 ms.addpath(f, f1, fo)
1614 progress.increment(item=f)
1616 progress.increment(item=f)
1615
1617
1616 # When merging in-memory, we can't support worker processes, so set the
1618 # When merging in-memory, we can't support worker processes, so set the
1617 # per-item cost at 0 in that case.
1619 # per-item cost at 0 in that case.
1618 cost = 0 if wctx.isinmemory() else 0.001
1620 cost = 0 if wctx.isinmemory() else 0.001
1619
1621
1620 # remove in parallel (must come before resolving path conflicts and getting)
1622 # remove in parallel (must come before resolving path conflicts and getting)
1621 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1623 prog = worker.worker(repo.ui, cost, batchremove, (repo, wctx),
1622 actions[ACTION_REMOVE])
1624 actions[ACTION_REMOVE])
1623 for i, item in prog:
1625 for i, item in prog:
1624 progress.increment(step=i, item=item)
1626 progress.increment(step=i, item=item)
1625 removed = len(actions[ACTION_REMOVE])
1627 removed = len(actions[ACTION_REMOVE])
1626
1628
1627 # resolve path conflicts (must come before getting)
1629 # resolve path conflicts (must come before getting)
1628 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1630 for f, args, msg in actions[ACTION_PATH_CONFLICT_RESOLVE]:
1629 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1631 repo.ui.debug(" %s: %s -> pr\n" % (f, msg))
1630 f0, = args
1632 f0, = args
1631 if wctx[f0].lexists():
1633 if wctx[f0].lexists():
1632 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1634 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1633 wctx[f].audit()
1635 wctx[f].audit()
1634 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1636 wctx[f].write(wctx.filectx(f0).data(), wctx.filectx(f0).flags())
1635 wctx[f0].remove()
1637 wctx[f0].remove()
1636 progress.increment(item=f)
1638 progress.increment(item=f)
1637
1639
1638 # get in parallel
1640 # get in parallel
1639 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
1641 prog = worker.worker(repo.ui, cost, batchget, (repo, mctx, wctx),
1640 actions[ACTION_GET])
1642 actions[ACTION_GET])
1641 for i, item in prog:
1643 for i, item in prog:
1642 progress.increment(step=i, item=item)
1644 progress.increment(step=i, item=item)
1643 updated = len(actions[ACTION_GET])
1645 updated = len(actions[ACTION_GET])
1644
1646
1645 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1647 if [a for a in actions[ACTION_GET] if a[0] == '.hgsubstate']:
1646 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1648 subrepoutil.submerge(repo, wctx, mctx, wctx, overwrite, labels)
1647
1649
1648 # forget (manifest only, just log it) (must come first)
1650 # forget (manifest only, just log it) (must come first)
1649 for f, args, msg in actions[ACTION_FORGET]:
1651 for f, args, msg in actions[ACTION_FORGET]:
1650 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1652 repo.ui.debug(" %s: %s -> f\n" % (f, msg))
1651 progress.increment(item=f)
1653 progress.increment(item=f)
1652
1654
1653 # re-add (manifest only, just log it)
1655 # re-add (manifest only, just log it)
1654 for f, args, msg in actions[ACTION_ADD]:
1656 for f, args, msg in actions[ACTION_ADD]:
1655 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1657 repo.ui.debug(" %s: %s -> a\n" % (f, msg))
1656 progress.increment(item=f)
1658 progress.increment(item=f)
1657
1659
1658 # re-add/mark as modified (manifest only, just log it)
1660 # re-add/mark as modified (manifest only, just log it)
1659 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1661 for f, args, msg in actions[ACTION_ADD_MODIFIED]:
1660 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1662 repo.ui.debug(" %s: %s -> am\n" % (f, msg))
1661 progress.increment(item=f)
1663 progress.increment(item=f)
1662
1664
1663 # keep (noop, just log it)
1665 # keep (noop, just log it)
1664 for f, args, msg in actions[ACTION_KEEP]:
1666 for f, args, msg in actions[ACTION_KEEP]:
1665 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1667 repo.ui.debug(" %s: %s -> k\n" % (f, msg))
1666 # no progress
1668 # no progress
1667
1669
1668 # directory rename, move local
1670 # directory rename, move local
1669 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1671 for f, args, msg in actions[ACTION_DIR_RENAME_MOVE_LOCAL]:
1670 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1672 repo.ui.debug(" %s: %s -> dm\n" % (f, msg))
1671 progress.increment(item=f)
1673 progress.increment(item=f)
1672 f0, flags = args
1674 f0, flags = args
1673 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1675 repo.ui.note(_("moving %s to %s\n") % (f0, f))
1674 wctx[f].audit()
1676 wctx[f].audit()
1675 wctx[f].write(wctx.filectx(f0).data(), flags)
1677 wctx[f].write(wctx.filectx(f0).data(), flags)
1676 wctx[f0].remove()
1678 wctx[f0].remove()
1677 updated += 1
1679 updated += 1
1678
1680
1679 # local directory rename, get
1681 # local directory rename, get
1680 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1682 for f, args, msg in actions[ACTION_LOCAL_DIR_RENAME_GET]:
1681 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1683 repo.ui.debug(" %s: %s -> dg\n" % (f, msg))
1682 progress.increment(item=f)
1684 progress.increment(item=f)
1683 f0, flags = args
1685 f0, flags = args
1684 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1686 repo.ui.note(_("getting %s to %s\n") % (f0, f))
1685 wctx[f].write(mctx.filectx(f0).data(), flags)
1687 wctx[f].write(mctx.filectx(f0).data(), flags)
1686 updated += 1
1688 updated += 1
1687
1689
1688 # exec
1690 # exec
1689 for f, args, msg in actions[ACTION_EXEC]:
1691 for f, args, msg in actions[ACTION_EXEC]:
1690 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1692 repo.ui.debug(" %s: %s -> e\n" % (f, msg))
1691 progress.increment(item=f)
1693 progress.increment(item=f)
1692 flags, = args
1694 flags, = args
1693 wctx[f].audit()
1695 wctx[f].audit()
1694 wctx[f].setflags('l' in flags, 'x' in flags)
1696 wctx[f].setflags('l' in flags, 'x' in flags)
1695 updated += 1
1697 updated += 1
1696
1698
1697 # the ordering is important here -- ms.mergedriver will raise if the merge
1699 # the ordering is important here -- ms.mergedriver will raise if the merge
1698 # driver has changed, and we want to be able to bypass it when overwrite is
1700 # driver has changed, and we want to be able to bypass it when overwrite is
1699 # True
1701 # True
1700 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1702 usemergedriver = not overwrite and mergeactions and ms.mergedriver
1701
1703
1702 if usemergedriver:
1704 if usemergedriver:
1703 if wctx.isinmemory():
1705 if wctx.isinmemory():
1704 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1706 raise error.InMemoryMergeConflictsError("in-memory merge does not "
1705 "support mergedriver")
1707 "support mergedriver")
1706 ms.commit()
1708 ms.commit()
1707 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1709 proceed = driverpreprocess(repo, ms, wctx, labels=labels)
1708 # the driver might leave some files unresolved
1710 # the driver might leave some files unresolved
1709 unresolvedf = set(ms.unresolved())
1711 unresolvedf = set(ms.unresolved())
1710 if not proceed:
1712 if not proceed:
1711 # XXX setting unresolved to at least 1 is a hack to make sure we
1713 # XXX setting unresolved to at least 1 is a hack to make sure we
1712 # error out
1714 # error out
1713 return updateresult(updated, merged, removed,
1715 return updateresult(updated, merged, removed,
1714 max(len(unresolvedf), 1))
1716 max(len(unresolvedf), 1))
1715 newactions = []
1717 newactions = []
1716 for f, args, msg in mergeactions:
1718 for f, args, msg in mergeactions:
1717 if f in unresolvedf:
1719 if f in unresolvedf:
1718 newactions.append((f, args, msg))
1720 newactions.append((f, args, msg))
1719 mergeactions = newactions
1721 mergeactions = newactions
1720
1722
1721 try:
1723 try:
1722 # premerge
1724 # premerge
1723 tocomplete = []
1725 tocomplete = []
1724 for f, args, msg in mergeactions:
1726 for f, args, msg in mergeactions:
1725 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1727 repo.ui.debug(" %s: %s -> m (premerge)\n" % (f, msg))
1726 progress.increment(item=f)
1728 progress.increment(item=f)
1727 if f == '.hgsubstate': # subrepo states need updating
1729 if f == '.hgsubstate': # subrepo states need updating
1728 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1730 subrepoutil.submerge(repo, wctx, mctx, wctx.ancestor(mctx),
1729 overwrite, labels)
1731 overwrite, labels)
1730 continue
1732 continue
1731 wctx[f].audit()
1733 wctx[f].audit()
1732 complete, r = ms.preresolve(f, wctx)
1734 complete, r = ms.preresolve(f, wctx)
1733 if not complete:
1735 if not complete:
1734 numupdates += 1
1736 numupdates += 1
1735 tocomplete.append((f, args, msg))
1737 tocomplete.append((f, args, msg))
1736
1738
1737 # merge
1739 # merge
1738 for f, args, msg in tocomplete:
1740 for f, args, msg in tocomplete:
1739 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1741 repo.ui.debug(" %s: %s -> m (merge)\n" % (f, msg))
1740 progress.increment(item=f, total=numupdates)
1742 progress.increment(item=f, total=numupdates)
1741 ms.resolve(f, wctx)
1743 ms.resolve(f, wctx)
1742
1744
1743 finally:
1745 finally:
1744 ms.commit()
1746 ms.commit()
1745
1747
1746 unresolved = ms.unresolvedcount()
1748 unresolved = ms.unresolvedcount()
1747
1749
1748 if (usemergedriver and not unresolved
1750 if (usemergedriver and not unresolved
1749 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1751 and ms.mdstate() != MERGE_DRIVER_STATE_SUCCESS):
1750 if not driverconclude(repo, ms, wctx, labels=labels):
1752 if not driverconclude(repo, ms, wctx, labels=labels):
1751 # XXX setting unresolved to at least 1 is a hack to make sure we
1753 # XXX setting unresolved to at least 1 is a hack to make sure we
1752 # error out
1754 # error out
1753 unresolved = max(unresolved, 1)
1755 unresolved = max(unresolved, 1)
1754
1756
1755 ms.commit()
1757 ms.commit()
1756
1758
1757 msupdated, msmerged, msremoved = ms.counts()
1759 msupdated, msmerged, msremoved = ms.counts()
1758 updated += msupdated
1760 updated += msupdated
1759 merged += msmerged
1761 merged += msmerged
1760 removed += msremoved
1762 removed += msremoved
1761
1763
1762 extraactions = ms.actions()
1764 extraactions = ms.actions()
1763 if extraactions:
1765 if extraactions:
1764 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1766 mfiles = set(a[0] for a in actions[ACTION_MERGE])
1765 for k, acts in extraactions.iteritems():
1767 for k, acts in extraactions.iteritems():
1766 actions[k].extend(acts)
1768 actions[k].extend(acts)
1767 # Remove these files from actions[ACTION_MERGE] as well. This is
1769 # Remove these files from actions[ACTION_MERGE] as well. This is
1768 # important because in recordupdates, files in actions[ACTION_MERGE]
1770 # important because in recordupdates, files in actions[ACTION_MERGE]
1769 # are processed after files in other actions, and the merge driver
1771 # are processed after files in other actions, and the merge driver
1770 # might add files to those actions via extraactions above. This can
1772 # might add files to those actions via extraactions above. This can
1771 # lead to a file being recorded twice, with poor results. This is
1773 # lead to a file being recorded twice, with poor results. This is
1772 # especially problematic for actions[ACTION_REMOVE] (currently only
1774 # especially problematic for actions[ACTION_REMOVE] (currently only
1773 # possible with the merge driver in the initial merge process;
1775 # possible with the merge driver in the initial merge process;
1774 # interrupted merges don't go through this flow).
1776 # interrupted merges don't go through this flow).
1775 #
1777 #
1776 # The real fix here is to have indexes by both file and action so
1778 # The real fix here is to have indexes by both file and action so
1777 # that when the action for a file is changed it is automatically
1779 # that when the action for a file is changed it is automatically
1778 # reflected in the other action lists. But that involves a more
1780 # reflected in the other action lists. But that involves a more
1779 # complex data structure, so this will do for now.
1781 # complex data structure, so this will do for now.
1780 #
1782 #
1781 # We don't need to do the same operation for 'dc' and 'cd' because
1783 # We don't need to do the same operation for 'dc' and 'cd' because
1782 # those lists aren't consulted again.
1784 # those lists aren't consulted again.
1783 mfiles.difference_update(a[0] for a in acts)
1785 mfiles.difference_update(a[0] for a in acts)
1784
1786
1785 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1787 actions[ACTION_MERGE] = [a for a in actions[ACTION_MERGE]
1786 if a[0] in mfiles]
1788 if a[0] in mfiles]
1787
1789
1788 progress.complete()
1790 progress.complete()
1789 return updateresult(updated, merged, removed, unresolved)
1791 return updateresult(updated, merged, removed, unresolved)
1790
1792
1791 def recordupdates(repo, actions, branchmerge):
1793 def recordupdates(repo, actions, branchmerge):
1792 "record merge actions to the dirstate"
1794 "record merge actions to the dirstate"
1793 # remove (must come first)
1795 # remove (must come first)
1794 for f, args, msg in actions.get(ACTION_REMOVE, []):
1796 for f, args, msg in actions.get(ACTION_REMOVE, []):
1795 if branchmerge:
1797 if branchmerge:
1796 repo.dirstate.remove(f)
1798 repo.dirstate.remove(f)
1797 else:
1799 else:
1798 repo.dirstate.drop(f)
1800 repo.dirstate.drop(f)
1799
1801
1800 # forget (must come first)
1802 # forget (must come first)
1801 for f, args, msg in actions.get(ACTION_FORGET, []):
1803 for f, args, msg in actions.get(ACTION_FORGET, []):
1802 repo.dirstate.drop(f)
1804 repo.dirstate.drop(f)
1803
1805
1804 # resolve path conflicts
1806 # resolve path conflicts
1805 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1807 for f, args, msg in actions.get(ACTION_PATH_CONFLICT_RESOLVE, []):
1806 f0, = args
1808 f0, = args
1807 origf0 = repo.dirstate.copied(f0) or f0
1809 origf0 = repo.dirstate.copied(f0) or f0
1808 repo.dirstate.add(f)
1810 repo.dirstate.add(f)
1809 repo.dirstate.copy(origf0, f)
1811 repo.dirstate.copy(origf0, f)
1810 if f0 == origf0:
1812 if f0 == origf0:
1811 repo.dirstate.remove(f0)
1813 repo.dirstate.remove(f0)
1812 else:
1814 else:
1813 repo.dirstate.drop(f0)
1815 repo.dirstate.drop(f0)
1814
1816
1815 # re-add
1817 # re-add
1816 for f, args, msg in actions.get(ACTION_ADD, []):
1818 for f, args, msg in actions.get(ACTION_ADD, []):
1817 repo.dirstate.add(f)
1819 repo.dirstate.add(f)
1818
1820
1819 # re-add/mark as modified
1821 # re-add/mark as modified
1820 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1822 for f, args, msg in actions.get(ACTION_ADD_MODIFIED, []):
1821 if branchmerge:
1823 if branchmerge:
1822 repo.dirstate.normallookup(f)
1824 repo.dirstate.normallookup(f)
1823 else:
1825 else:
1824 repo.dirstate.add(f)
1826 repo.dirstate.add(f)
1825
1827
1826 # exec change
1828 # exec change
1827 for f, args, msg in actions.get(ACTION_EXEC, []):
1829 for f, args, msg in actions.get(ACTION_EXEC, []):
1828 repo.dirstate.normallookup(f)
1830 repo.dirstate.normallookup(f)
1829
1831
1830 # keep
1832 # keep
1831 for f, args, msg in actions.get(ACTION_KEEP, []):
1833 for f, args, msg in actions.get(ACTION_KEEP, []):
1832 pass
1834 pass
1833
1835
1834 # get
1836 # get
1835 for f, args, msg in actions.get(ACTION_GET, []):
1837 for f, args, msg in actions.get(ACTION_GET, []):
1836 if branchmerge:
1838 if branchmerge:
1837 repo.dirstate.otherparent(f)
1839 repo.dirstate.otherparent(f)
1838 else:
1840 else:
1839 repo.dirstate.normal(f)
1841 repo.dirstate.normal(f)
1840
1842
1841 # merge
1843 # merge
1842 for f, args, msg in actions.get(ACTION_MERGE, []):
1844 for f, args, msg in actions.get(ACTION_MERGE, []):
1843 f1, f2, fa, move, anc = args
1845 f1, f2, fa, move, anc = args
1844 if branchmerge:
1846 if branchmerge:
1845 # We've done a branch merge, mark this file as merged
1847 # We've done a branch merge, mark this file as merged
1846 # so that we properly record the merger later
1848 # so that we properly record the merger later
1847 repo.dirstate.merge(f)
1849 repo.dirstate.merge(f)
1848 if f1 != f2: # copy/rename
1850 if f1 != f2: # copy/rename
1849 if move:
1851 if move:
1850 repo.dirstate.remove(f1)
1852 repo.dirstate.remove(f1)
1851 if f1 != f:
1853 if f1 != f:
1852 repo.dirstate.copy(f1, f)
1854 repo.dirstate.copy(f1, f)
1853 else:
1855 else:
1854 repo.dirstate.copy(f2, f)
1856 repo.dirstate.copy(f2, f)
1855 else:
1857 else:
1856 # We've update-merged a locally modified file, so
1858 # We've update-merged a locally modified file, so
1857 # we set the dirstate to emulate a normal checkout
1859 # we set the dirstate to emulate a normal checkout
1858 # of that file some time in the past. Thus our
1860 # of that file some time in the past. Thus our
1859 # merge will appear as a normal local file
1861 # merge will appear as a normal local file
1860 # modification.
1862 # modification.
1861 if f2 == f: # file not locally copied/moved
1863 if f2 == f: # file not locally copied/moved
1862 repo.dirstate.normallookup(f)
1864 repo.dirstate.normallookup(f)
1863 if move:
1865 if move:
1864 repo.dirstate.drop(f1)
1866 repo.dirstate.drop(f1)
1865
1867
1866 # directory rename, move local
1868 # directory rename, move local
1867 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1869 for f, args, msg in actions.get(ACTION_DIR_RENAME_MOVE_LOCAL, []):
1868 f0, flag = args
1870 f0, flag = args
1869 if branchmerge:
1871 if branchmerge:
1870 repo.dirstate.add(f)
1872 repo.dirstate.add(f)
1871 repo.dirstate.remove(f0)
1873 repo.dirstate.remove(f0)
1872 repo.dirstate.copy(f0, f)
1874 repo.dirstate.copy(f0, f)
1873 else:
1875 else:
1874 repo.dirstate.normal(f)
1876 repo.dirstate.normal(f)
1875 repo.dirstate.drop(f0)
1877 repo.dirstate.drop(f0)
1876
1878
1877 # directory rename, get
1879 # directory rename, get
1878 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1880 for f, args, msg in actions.get(ACTION_LOCAL_DIR_RENAME_GET, []):
1879 f0, flag = args
1881 f0, flag = args
1880 if branchmerge:
1882 if branchmerge:
1881 repo.dirstate.add(f)
1883 repo.dirstate.add(f)
1882 repo.dirstate.copy(f0, f)
1884 repo.dirstate.copy(f0, f)
1883 else:
1885 else:
1884 repo.dirstate.normal(f)
1886 repo.dirstate.normal(f)
1885
1887
1886 def update(repo, node, branchmerge, force, ancestor=None,
1888 def update(repo, node, branchmerge, force, ancestor=None,
1887 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1889 mergeancestor=False, labels=None, matcher=None, mergeforce=False,
1888 updatecheck=None, wc=None):
1890 updatecheck=None, wc=None):
1889 """
1891 """
1890 Perform a merge between the working directory and the given node
1892 Perform a merge between the working directory and the given node
1891
1893
1892 node = the node to update to
1894 node = the node to update to
1893 branchmerge = whether to merge between branches
1895 branchmerge = whether to merge between branches
1894 force = whether to force branch merging or file overwriting
1896 force = whether to force branch merging or file overwriting
1895 matcher = a matcher to filter file lists (dirstate not updated)
1897 matcher = a matcher to filter file lists (dirstate not updated)
1896 mergeancestor = whether it is merging with an ancestor. If true,
1898 mergeancestor = whether it is merging with an ancestor. If true,
1897 we should accept the incoming changes for any prompts that occur.
1899 we should accept the incoming changes for any prompts that occur.
1898 If false, merging with an ancestor (fast-forward) is only allowed
1900 If false, merging with an ancestor (fast-forward) is only allowed
1899 between different named branches. This flag is used by rebase extension
1901 between different named branches. This flag is used by rebase extension
1900 as a temporary fix and should be avoided in general.
1902 as a temporary fix and should be avoided in general.
1901 labels = labels to use for base, local and other
1903 labels = labels to use for base, local and other
1902 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1904 mergeforce = whether the merge was run with 'merge --force' (deprecated): if
1903 this is True, then 'force' should be True as well.
1905 this is True, then 'force' should be True as well.
1904
1906
1905 The table below shows all the behaviors of the update command given the
1907 The table below shows all the behaviors of the update command given the
1906 -c/--check and -C/--clean or no options, whether the working directory is
1908 -c/--check and -C/--clean or no options, whether the working directory is
1907 dirty, whether a revision is specified, and the relationship of the parent
1909 dirty, whether a revision is specified, and the relationship of the parent
1908 rev to the target rev (linear or not). Match from top first. The -n
1910 rev to the target rev (linear or not). Match from top first. The -n
1909 option doesn't exist on the command line, but represents the
1911 option doesn't exist on the command line, but represents the
1910 experimental.updatecheck=noconflict option.
1912 experimental.updatecheck=noconflict option.
1911
1913
1912 This logic is tested by test-update-branches.t.
1914 This logic is tested by test-update-branches.t.
1913
1915
1914 -c -C -n -m dirty rev linear | result
1916 -c -C -n -m dirty rev linear | result
1915 y y * * * * * | (1)
1917 y y * * * * * | (1)
1916 y * y * * * * | (1)
1918 y * y * * * * | (1)
1917 y * * y * * * | (1)
1919 y * * y * * * | (1)
1918 * y y * * * * | (1)
1920 * y y * * * * | (1)
1919 * y * y * * * | (1)
1921 * y * y * * * | (1)
1920 * * y y * * * | (1)
1922 * * y y * * * | (1)
1921 * * * * * n n | x
1923 * * * * * n n | x
1922 * * * * n * * | ok
1924 * * * * n * * | ok
1923 n n n n y * y | merge
1925 n n n n y * y | merge
1924 n n n n y y n | (2)
1926 n n n n y y n | (2)
1925 n n n y y * * | merge
1927 n n n y y * * | merge
1926 n n y n y * * | merge if no conflict
1928 n n y n y * * | merge if no conflict
1927 n y n n y * * | discard
1929 n y n n y * * | discard
1928 y n n n y * * | (3)
1930 y n n n y * * | (3)
1929
1931
1930 x = can't happen
1932 x = can't happen
1931 * = don't-care
1933 * = don't-care
1932 1 = incompatible options (checked in commands.py)
1934 1 = incompatible options (checked in commands.py)
1933 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1935 2 = abort: uncommitted changes (commit or update --clean to discard changes)
1934 3 = abort: uncommitted changes (checked in commands.py)
1936 3 = abort: uncommitted changes (checked in commands.py)
1935
1937
1936 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1938 The merge is performed inside ``wc``, a workingctx-like objects. It defaults
1937 to repo[None] if None is passed.
1939 to repo[None] if None is passed.
1938
1940
1939 Return the same tuple as applyupdates().
1941 Return the same tuple as applyupdates().
1940 """
1942 """
1941 # Avoid cycle.
1943 # Avoid cycle.
1942 from . import sparse
1944 from . import sparse
1943
1945
1944 # This function used to find the default destination if node was None, but
1946 # This function used to find the default destination if node was None, but
1945 # that's now in destutil.py.
1947 # that's now in destutil.py.
1946 assert node is not None
1948 assert node is not None
1947 if not branchmerge and not force:
1949 if not branchmerge and not force:
1948 # TODO: remove the default once all callers that pass branchmerge=False
1950 # TODO: remove the default once all callers that pass branchmerge=False
1949 # and force=False pass a value for updatecheck. We may want to allow
1951 # and force=False pass a value for updatecheck. We may want to allow
1950 # updatecheck='abort' to better suppport some of these callers.
1952 # updatecheck='abort' to better suppport some of these callers.
1951 if updatecheck is None:
1953 if updatecheck is None:
1952 updatecheck = 'linear'
1954 updatecheck = 'linear'
1953 assert updatecheck in ('none', 'linear', 'noconflict')
1955 assert updatecheck in ('none', 'linear', 'noconflict')
1954 # If we're doing a partial update, we need to skip updating
1956 # If we're doing a partial update, we need to skip updating
1955 # the dirstate, so make a note of any partial-ness to the
1957 # the dirstate, so make a note of any partial-ness to the
1956 # update here.
1958 # update here.
1957 if matcher is None or matcher.always():
1959 if matcher is None or matcher.always():
1958 partial = False
1960 partial = False
1959 else:
1961 else:
1960 partial = True
1962 partial = True
1961 with repo.wlock():
1963 with repo.wlock():
1962 if wc is None:
1964 if wc is None:
1963 wc = repo[None]
1965 wc = repo[None]
1964 pl = wc.parents()
1966 pl = wc.parents()
1965 p1 = pl[0]
1967 p1 = pl[0]
1966 pas = [None]
1968 pas = [None]
1967 if ancestor is not None:
1969 if ancestor is not None:
1968 pas = [repo[ancestor]]
1970 pas = [repo[ancestor]]
1969
1971
1970 overwrite = force and not branchmerge
1972 overwrite = force and not branchmerge
1971
1973
1972 p2 = repo[node]
1974 p2 = repo[node]
1973 if pas[0] is None:
1975 if pas[0] is None:
1974 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1976 if repo.ui.configlist('merge', 'preferancestor') == ['*']:
1975 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1977 cahs = repo.changelog.commonancestorsheads(p1.node(), p2.node())
1976 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1978 pas = [repo[anc] for anc in (sorted(cahs) or [nullid])]
1977 else:
1979 else:
1978 pas = [p1.ancestor(p2, warn=branchmerge)]
1980 pas = [p1.ancestor(p2, warn=branchmerge)]
1979
1981
1980 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1982 fp1, fp2, xp1, xp2 = p1.node(), p2.node(), bytes(p1), bytes(p2)
1981
1983
1982 ### check phase
1984 ### check phase
1983 if not overwrite:
1985 if not overwrite:
1984 if len(pl) > 1:
1986 if len(pl) > 1:
1985 raise error.Abort(_("outstanding uncommitted merge"))
1987 raise error.Abort(_("outstanding uncommitted merge"))
1986 ms = mergestate.read(repo)
1988 ms = mergestate.read(repo)
1987 if list(ms.unresolved()):
1989 if list(ms.unresolved()):
1988 raise error.Abort(_("outstanding merge conflicts"))
1990 raise error.Abort(_("outstanding merge conflicts"))
1989 if branchmerge:
1991 if branchmerge:
1990 if pas == [p2]:
1992 if pas == [p2]:
1991 raise error.Abort(_("merging with a working directory ancestor"
1993 raise error.Abort(_("merging with a working directory ancestor"
1992 " has no effect"))
1994 " has no effect"))
1993 elif pas == [p1]:
1995 elif pas == [p1]:
1994 if not mergeancestor and wc.branch() == p2.branch():
1996 if not mergeancestor and wc.branch() == p2.branch():
1995 raise error.Abort(_("nothing to merge"),
1997 raise error.Abort(_("nothing to merge"),
1996 hint=_("use 'hg update' "
1998 hint=_("use 'hg update' "
1997 "or check 'hg heads'"))
1999 "or check 'hg heads'"))
1998 if not force and (wc.files() or wc.deleted()):
2000 if not force and (wc.files() or wc.deleted()):
1999 raise error.Abort(_("uncommitted changes"),
2001 raise error.Abort(_("uncommitted changes"),
2000 hint=_("use 'hg status' to list changes"))
2002 hint=_("use 'hg status' to list changes"))
2001 if not wc.isinmemory():
2003 if not wc.isinmemory():
2002 for s in sorted(wc.substate):
2004 for s in sorted(wc.substate):
2003 wc.sub(s).bailifchanged()
2005 wc.sub(s).bailifchanged()
2004
2006
2005 elif not overwrite:
2007 elif not overwrite:
2006 if p1 == p2: # no-op update
2008 if p1 == p2: # no-op update
2007 # call the hooks and exit early
2009 # call the hooks and exit early
2008 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2010 repo.hook('preupdate', throw=True, parent1=xp2, parent2='')
2009 repo.hook('update', parent1=xp2, parent2='', error=0)
2011 repo.hook('update', parent1=xp2, parent2='', error=0)
2010 return updateresult(0, 0, 0, 0)
2012 return updateresult(0, 0, 0, 0)
2011
2013
2012 if (updatecheck == 'linear' and
2014 if (updatecheck == 'linear' and
2013 pas not in ([p1], [p2])): # nonlinear
2015 pas not in ([p1], [p2])): # nonlinear
2014 dirty = wc.dirty(missing=True)
2016 dirty = wc.dirty(missing=True)
2015 if dirty:
2017 if dirty:
2016 # Branching is a bit strange to ensure we do the minimal
2018 # Branching is a bit strange to ensure we do the minimal
2017 # amount of call to obsutil.foreground.
2019 # amount of call to obsutil.foreground.
2018 foreground = obsutil.foreground(repo, [p1.node()])
2020 foreground = obsutil.foreground(repo, [p1.node()])
2019 # note: the <node> variable contains a random identifier
2021 # note: the <node> variable contains a random identifier
2020 if repo[node].node() in foreground:
2022 if repo[node].node() in foreground:
2021 pass # allow updating to successors
2023 pass # allow updating to successors
2022 else:
2024 else:
2023 msg = _("uncommitted changes")
2025 msg = _("uncommitted changes")
2024 hint = _("commit or update --clean to discard changes")
2026 hint = _("commit or update --clean to discard changes")
2025 raise error.UpdateAbort(msg, hint=hint)
2027 raise error.UpdateAbort(msg, hint=hint)
2026 else:
2028 else:
2027 # Allow jumping branches if clean and specific rev given
2029 # Allow jumping branches if clean and specific rev given
2028 pass
2030 pass
2029
2031
2030 if overwrite:
2032 if overwrite:
2031 pas = [wc]
2033 pas = [wc]
2032 elif not branchmerge:
2034 elif not branchmerge:
2033 pas = [p1]
2035 pas = [p1]
2034
2036
2035 # deprecated config: merge.followcopies
2037 # deprecated config: merge.followcopies
2036 followcopies = repo.ui.configbool('merge', 'followcopies')
2038 followcopies = repo.ui.configbool('merge', 'followcopies')
2037 if overwrite:
2039 if overwrite:
2038 followcopies = False
2040 followcopies = False
2039 elif not pas[0]:
2041 elif not pas[0]:
2040 followcopies = False
2042 followcopies = False
2041 if not branchmerge and not wc.dirty(missing=True):
2043 if not branchmerge and not wc.dirty(missing=True):
2042 followcopies = False
2044 followcopies = False
2043
2045
2044 ### calculate phase
2046 ### calculate phase
2045 actionbyfile, diverge, renamedelete = calculateupdates(
2047 actionbyfile, diverge, renamedelete = calculateupdates(
2046 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2048 repo, wc, p2, pas, branchmerge, force, mergeancestor,
2047 followcopies, matcher=matcher, mergeforce=mergeforce)
2049 followcopies, matcher=matcher, mergeforce=mergeforce)
2048
2050
2049 if updatecheck == 'noconflict':
2051 if updatecheck == 'noconflict':
2050 for f, (m, args, msg) in actionbyfile.iteritems():
2052 for f, (m, args, msg) in actionbyfile.iteritems():
2051 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2053 if m not in (ACTION_GET, ACTION_KEEP, ACTION_EXEC,
2052 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2054 ACTION_REMOVE, ACTION_PATH_CONFLICT_RESOLVE):
2053 msg = _("conflicting changes")
2055 msg = _("conflicting changes")
2054 hint = _("commit or update --clean to discard changes")
2056 hint = _("commit or update --clean to discard changes")
2055 raise error.Abort(msg, hint=hint)
2057 raise error.Abort(msg, hint=hint)
2056
2058
2057 # Prompt and create actions. Most of this is in the resolve phase
2059 # Prompt and create actions. Most of this is in the resolve phase
2058 # already, but we can't handle .hgsubstate in filemerge or
2060 # already, but we can't handle .hgsubstate in filemerge or
2059 # subrepoutil.submerge yet so we have to keep prompting for it.
2061 # subrepoutil.submerge yet so we have to keep prompting for it.
2060 if '.hgsubstate' in actionbyfile:
2062 if '.hgsubstate' in actionbyfile:
2061 f = '.hgsubstate'
2063 f = '.hgsubstate'
2062 m, args, msg = actionbyfile[f]
2064 m, args, msg = actionbyfile[f]
2063 prompts = filemerge.partextras(labels)
2065 prompts = filemerge.partextras(labels)
2064 prompts['f'] = f
2066 prompts['f'] = f
2065 if m == ACTION_CHANGED_DELETED:
2067 if m == ACTION_CHANGED_DELETED:
2066 if repo.ui.promptchoice(
2068 if repo.ui.promptchoice(
2067 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2069 _("local%(l)s changed %(f)s which other%(o)s deleted\n"
2068 "use (c)hanged version or (d)elete?"
2070 "use (c)hanged version or (d)elete?"
2069 "$$ &Changed $$ &Delete") % prompts, 0):
2071 "$$ &Changed $$ &Delete") % prompts, 0):
2070 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2072 actionbyfile[f] = (ACTION_REMOVE, None, 'prompt delete')
2071 elif f in p1:
2073 elif f in p1:
2072 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2074 actionbyfile[f] = (ACTION_ADD_MODIFIED, None, 'prompt keep')
2073 else:
2075 else:
2074 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2076 actionbyfile[f] = (ACTION_ADD, None, 'prompt keep')
2075 elif m == ACTION_DELETED_CHANGED:
2077 elif m == ACTION_DELETED_CHANGED:
2076 f1, f2, fa, move, anc = args
2078 f1, f2, fa, move, anc = args
2077 flags = p2[f2].flags()
2079 flags = p2[f2].flags()
2078 if repo.ui.promptchoice(
2080 if repo.ui.promptchoice(
2079 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2081 _("other%(o)s changed %(f)s which local%(l)s deleted\n"
2080 "use (c)hanged version or leave (d)eleted?"
2082 "use (c)hanged version or leave (d)eleted?"
2081 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2083 "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
2082 actionbyfile[f] = (ACTION_GET, (flags, False),
2084 actionbyfile[f] = (ACTION_GET, (flags, False),
2083 'prompt recreating')
2085 'prompt recreating')
2084 else:
2086 else:
2085 del actionbyfile[f]
2087 del actionbyfile[f]
2086
2088
2087 # Convert to dictionary-of-lists format
2089 # Convert to dictionary-of-lists format
2088 actions = dict((m, [])
2090 actions = dict((m, [])
2089 for m in (
2091 for m in (
2090 ACTION_ADD,
2092 ACTION_ADD,
2091 ACTION_ADD_MODIFIED,
2093 ACTION_ADD_MODIFIED,
2092 ACTION_FORGET,
2094 ACTION_FORGET,
2093 ACTION_GET,
2095 ACTION_GET,
2094 ACTION_CHANGED_DELETED,
2096 ACTION_CHANGED_DELETED,
2095 ACTION_DELETED_CHANGED,
2097 ACTION_DELETED_CHANGED,
2096 ACTION_REMOVE,
2098 ACTION_REMOVE,
2097 ACTION_DIR_RENAME_MOVE_LOCAL,
2099 ACTION_DIR_RENAME_MOVE_LOCAL,
2098 ACTION_LOCAL_DIR_RENAME_GET,
2100 ACTION_LOCAL_DIR_RENAME_GET,
2099 ACTION_MERGE,
2101 ACTION_MERGE,
2100 ACTION_EXEC,
2102 ACTION_EXEC,
2101 ACTION_KEEP,
2103 ACTION_KEEP,
2102 ACTION_PATH_CONFLICT,
2104 ACTION_PATH_CONFLICT,
2103 ACTION_PATH_CONFLICT_RESOLVE))
2105 ACTION_PATH_CONFLICT_RESOLVE))
2104 for f, (m, args, msg) in actionbyfile.iteritems():
2106 for f, (m, args, msg) in actionbyfile.iteritems():
2105 if m not in actions:
2107 if m not in actions:
2106 actions[m] = []
2108 actions[m] = []
2107 actions[m].append((f, args, msg))
2109 actions[m].append((f, args, msg))
2108
2110
2109 if not util.fscasesensitive(repo.path):
2111 if not util.fscasesensitive(repo.path):
2110 # check collision between files only in p2 for clean update
2112 # check collision between files only in p2 for clean update
2111 if (not branchmerge and
2113 if (not branchmerge and
2112 (force or not wc.dirty(missing=True, branch=False))):
2114 (force or not wc.dirty(missing=True, branch=False))):
2113 _checkcollision(repo, p2.manifest(), None)
2115 _checkcollision(repo, p2.manifest(), None)
2114 else:
2116 else:
2115 _checkcollision(repo, wc.manifest(), actions)
2117 _checkcollision(repo, wc.manifest(), actions)
2116
2118
2117 # divergent renames
2119 # divergent renames
2118 for f, fl in sorted(diverge.iteritems()):
2120 for f, fl in sorted(diverge.iteritems()):
2119 repo.ui.warn(_("note: possible conflict - %s was renamed "
2121 repo.ui.warn(_("note: possible conflict - %s was renamed "
2120 "multiple times to:\n") % f)
2122 "multiple times to:\n") % f)
2121 for nf in fl:
2123 for nf in fl:
2122 repo.ui.warn(" %s\n" % nf)
2124 repo.ui.warn(" %s\n" % nf)
2123
2125
2124 # rename and delete
2126 # rename and delete
2125 for f, fl in sorted(renamedelete.iteritems()):
2127 for f, fl in sorted(renamedelete.iteritems()):
2126 repo.ui.warn(_("note: possible conflict - %s was deleted "
2128 repo.ui.warn(_("note: possible conflict - %s was deleted "
2127 "and renamed to:\n") % f)
2129 "and renamed to:\n") % f)
2128 for nf in fl:
2130 for nf in fl:
2129 repo.ui.warn(" %s\n" % nf)
2131 repo.ui.warn(" %s\n" % nf)
2130
2132
2131 ### apply phase
2133 ### apply phase
2132 if not branchmerge: # just jump to the new rev
2134 if not branchmerge: # just jump to the new rev
2133 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2135 fp1, fp2, xp1, xp2 = fp2, nullid, xp2, ''
2134 if not partial and not wc.isinmemory():
2136 if not partial and not wc.isinmemory():
2135 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2137 repo.hook('preupdate', throw=True, parent1=xp1, parent2=xp2)
2136 # note that we're in the middle of an update
2138 # note that we're in the middle of an update
2137 repo.vfs.write('updatestate', p2.hex())
2139 repo.vfs.write('updatestate', p2.hex())
2138
2140
2139 # Advertise fsmonitor when its presence could be useful.
2141 # Advertise fsmonitor when its presence could be useful.
2140 #
2142 #
2141 # We only advertise when performing an update from an empty working
2143 # We only advertise when performing an update from an empty working
2142 # directory. This typically only occurs during initial clone.
2144 # directory. This typically only occurs during initial clone.
2143 #
2145 #
2144 # We give users a mechanism to disable the warning in case it is
2146 # We give users a mechanism to disable the warning in case it is
2145 # annoying.
2147 # annoying.
2146 #
2148 #
2147 # We only allow on Linux and MacOS because that's where fsmonitor is
2149 # We only allow on Linux and MacOS because that's where fsmonitor is
2148 # considered stable.
2150 # considered stable.
2149 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2151 fsmonitorwarning = repo.ui.configbool('fsmonitor', 'warn_when_unused')
2150 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2152 fsmonitorthreshold = repo.ui.configint('fsmonitor',
2151 'warn_update_file_count')
2153 'warn_update_file_count')
2152 try:
2154 try:
2153 # avoid cycle: extensions -> cmdutil -> merge
2155 # avoid cycle: extensions -> cmdutil -> merge
2154 from . import extensions
2156 from . import extensions
2155 extensions.find('fsmonitor')
2157 extensions.find('fsmonitor')
2156 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2158 fsmonitorenabled = repo.ui.config('fsmonitor', 'mode') != 'off'
2157 # We intentionally don't look at whether fsmonitor has disabled
2159 # We intentionally don't look at whether fsmonitor has disabled
2158 # itself because a) fsmonitor may have already printed a warning
2160 # itself because a) fsmonitor may have already printed a warning
2159 # b) we only care about the config state here.
2161 # b) we only care about the config state here.
2160 except KeyError:
2162 except KeyError:
2161 fsmonitorenabled = False
2163 fsmonitorenabled = False
2162
2164
2163 if (fsmonitorwarning
2165 if (fsmonitorwarning
2164 and not fsmonitorenabled
2166 and not fsmonitorenabled
2165 and p1.node() == nullid
2167 and p1.node() == nullid
2166 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2168 and len(actions[ACTION_GET]) >= fsmonitorthreshold
2167 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2169 and pycompat.sysplatform.startswith(('linux', 'darwin'))):
2168 repo.ui.warn(
2170 repo.ui.warn(
2169 _('(warning: large working directory being used without '
2171 _('(warning: large working directory being used without '
2170 'fsmonitor enabled; enable fsmonitor to improve performance; '
2172 'fsmonitor enabled; enable fsmonitor to improve performance; '
2171 'see "hg help -e fsmonitor")\n'))
2173 'see "hg help -e fsmonitor")\n'))
2172
2174
2173 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
2175 stats = applyupdates(repo, actions, wc, p2, overwrite, labels=labels)
2174
2176
2175 if not partial and not wc.isinmemory():
2177 if not partial and not wc.isinmemory():
2176 with repo.dirstate.parentchange():
2178 with repo.dirstate.parentchange():
2177 repo.setparents(fp1, fp2)
2179 repo.setparents(fp1, fp2)
2178 recordupdates(repo, actions, branchmerge)
2180 recordupdates(repo, actions, branchmerge)
2179 # update completed, clear state
2181 # update completed, clear state
2180 util.unlink(repo.vfs.join('updatestate'))
2182 util.unlink(repo.vfs.join('updatestate'))
2181
2183
2182 if not branchmerge:
2184 if not branchmerge:
2183 repo.dirstate.setbranch(p2.branch())
2185 repo.dirstate.setbranch(p2.branch())
2184
2186
2185 # If we're updating to a location, clean up any stale temporary includes
2187 # If we're updating to a location, clean up any stale temporary includes
2186 # (ex: this happens during hg rebase --abort).
2188 # (ex: this happens during hg rebase --abort).
2187 if not branchmerge:
2189 if not branchmerge:
2188 sparse.prunetemporaryincludes(repo)
2190 sparse.prunetemporaryincludes(repo)
2189
2191
2190 if not partial:
2192 if not partial:
2191 repo.hook('update', parent1=xp1, parent2=xp2,
2193 repo.hook('update', parent1=xp1, parent2=xp2,
2192 error=stats.unresolvedcount)
2194 error=stats.unresolvedcount)
2193 return stats
2195 return stats
2194
2196
2195 def graft(repo, ctx, pctx, labels, keepparent=False):
2197 def graft(repo, ctx, pctx, labels, keepparent=False):
2196 """Do a graft-like merge.
2198 """Do a graft-like merge.
2197
2199
2198 This is a merge where the merge ancestor is chosen such that one
2200 This is a merge where the merge ancestor is chosen such that one
2199 or more changesets are grafted onto the current changeset. In
2201 or more changesets are grafted onto the current changeset. In
2200 addition to the merge, this fixes up the dirstate to include only
2202 addition to the merge, this fixes up the dirstate to include only
2201 a single parent (if keepparent is False) and tries to duplicate any
2203 a single parent (if keepparent is False) and tries to duplicate any
2202 renames/copies appropriately.
2204 renames/copies appropriately.
2203
2205
2204 ctx - changeset to rebase
2206 ctx - changeset to rebase
2205 pctx - merge base, usually ctx.p1()
2207 pctx - merge base, usually ctx.p1()
2206 labels - merge labels eg ['local', 'graft']
2208 labels - merge labels eg ['local', 'graft']
2207 keepparent - keep second parent if any
2209 keepparent - keep second parent if any
2208
2210
2209 """
2211 """
2210 # If we're grafting a descendant onto an ancestor, be sure to pass
2212 # If we're grafting a descendant onto an ancestor, be sure to pass
2211 # mergeancestor=True to update. This does two things: 1) allows the merge if
2213 # mergeancestor=True to update. This does two things: 1) allows the merge if
2212 # the destination is the same as the parent of the ctx (so we can use graft
2214 # the destination is the same as the parent of the ctx (so we can use graft
2213 # to copy commits), and 2) informs update that the incoming changes are
2215 # to copy commits), and 2) informs update that the incoming changes are
2214 # newer than the destination so it doesn't prompt about "remote changed foo
2216 # newer than the destination so it doesn't prompt about "remote changed foo
2215 # which local deleted".
2217 # which local deleted".
2216 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2218 mergeancestor = repo.changelog.isancestor(repo['.'].node(), ctx.node())
2217
2219
2218 stats = update(repo, ctx.node(), True, True, pctx.node(),
2220 stats = update(repo, ctx.node(), True, True, pctx.node(),
2219 mergeancestor=mergeancestor, labels=labels)
2221 mergeancestor=mergeancestor, labels=labels)
2220
2222
2221 pother = nullid
2223 pother = nullid
2222 parents = ctx.parents()
2224 parents = ctx.parents()
2223 if keepparent and len(parents) == 2 and pctx in parents:
2225 if keepparent and len(parents) == 2 and pctx in parents:
2224 parents.remove(pctx)
2226 parents.remove(pctx)
2225 pother = parents[0].node()
2227 pother = parents[0].node()
2226
2228
2227 with repo.dirstate.parentchange():
2229 with repo.dirstate.parentchange():
2228 repo.setparents(repo['.'].node(), pother)
2230 repo.setparents(repo['.'].node(), pother)
2229 repo.dirstate.write(repo.currenttransaction())
2231 repo.dirstate.write(repo.currenttransaction())
2230 # fix up dirstate for copies and renames
2232 # fix up dirstate for copies and renames
2231 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2233 copies.duplicatecopies(repo, repo[None], ctx.rev(), pctx.rev())
2232 return stats
2234 return stats
@@ -1,426 +1,434 b''
1 #require tic
1 #require tic
2
2
3 Set up a repo
3 Set up a repo
4
4
5 $ cp $HGRCPATH $HGRCPATH.pretest
5 $ cp $HGRCPATH $HGRCPATH.pretest
6 $ cat <<EOF >> $HGRCPATH
6 $ cat <<EOF >> $HGRCPATH
7 > [ui]
7 > [ui]
8 > interactive = true
8 > interactive = true
9 > interface = curses
9 > interface = curses
10 > [experimental]
10 > [experimental]
11 > crecordtest = testModeCommands
11 > crecordtest = testModeCommands
12 > EOF
12 > EOF
13
13
14 Record with noeol at eof (issue5268)
14 Record with noeol at eof (issue5268)
15 $ hg init noeol
15 $ hg init noeol
16 $ cd noeol
16 $ cd noeol
17 $ printf '0' > a
17 $ printf '0' > a
18 $ printf '0\n' > b
18 $ printf '0\n' > b
19 $ hg ci -Aqm initial
19 $ hg ci -Aqm initial
20 $ printf '1\n0' > a
20 $ printf '1\n0' > a
21 $ printf '1\n0\n' > b
21 $ printf '1\n0\n' > b
22 $ cat <<EOF >testModeCommands
22 $ cat <<EOF >testModeCommands
23 > c
23 > c
24 > EOF
24 > EOF
25 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "add hunks" -d "0 0"
25 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "add hunks" -d "0 0"
26 $ cd ..
26 $ cd ..
27
27
28 Normal repo
28 Normal repo
29 $ hg init a
29 $ hg init a
30 $ cd a
30 $ cd a
31
31
32 Committing some changes but stopping on the way
32 Committing some changes but stopping on the way
33
33
34 $ echo "a" > a
34 $ echo "a" > a
35 $ hg add a
35 $ hg add a
36 $ cat <<EOF >testModeCommands
36 $ cat <<EOF >testModeCommands
37 > TOGGLE
37 > TOGGLE
38 > X
38 > X
39 > EOF
39 > EOF
40 $ hg commit -i -m "a" -d "0 0"
40 $ hg commit -i -m "a" -d "0 0"
41 no changes to record
41 no changes to record
42 [1]
42 [1]
43 $ hg tip
43 $ hg tip
44 changeset: -1:000000000000
44 changeset: -1:000000000000
45 tag: tip
45 tag: tip
46 user:
46 user:
47 date: Thu Jan 01 00:00:00 1970 +0000
47 date: Thu Jan 01 00:00:00 1970 +0000
48
48
49
49
50 Committing some changes
50 Committing some changes
51
51
52 $ cat <<EOF >testModeCommands
52 $ cat <<EOF >testModeCommands
53 > X
53 > X
54 > EOF
54 > EOF
55 $ hg commit -i -m "a" -d "0 0"
55 $ hg commit -i -m "a" -d "0 0"
56 $ hg tip
56 $ hg tip
57 changeset: 0:cb9a9f314b8b
57 changeset: 0:cb9a9f314b8b
58 tag: tip
58 tag: tip
59 user: test
59 user: test
60 date: Thu Jan 01 00:00:00 1970 +0000
60 date: Thu Jan 01 00:00:00 1970 +0000
61 summary: a
61 summary: a
62
62
63 Check that commit -i works with no changes
63 Check that commit -i works with no changes
64 $ hg commit -i
64 $ hg commit -i
65 no changes to record
65 no changes to record
66 [1]
66 [1]
67
67
68 Committing only one file
68 Committing only one file
69
69
70 $ echo "a" >> a
70 $ echo "a" >> a
71 >>> open('b', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
71 >>> open('b', 'wb').write(b"1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n") and None
72 $ hg add b
72 $ hg add b
73 $ cat <<EOF >testModeCommands
73 $ cat <<EOF >testModeCommands
74 > TOGGLE
74 > TOGGLE
75 > KEY_DOWN
75 > KEY_DOWN
76 > X
76 > X
77 > EOF
77 > EOF
78 $ hg commit -i -m "one file" -d "0 0"
78 $ hg commit -i -m "one file" -d "0 0"
79 $ hg tip
79 $ hg tip
80 changeset: 1:fb2705a663ea
80 changeset: 1:fb2705a663ea
81 tag: tip
81 tag: tip
82 user: test
82 user: test
83 date: Thu Jan 01 00:00:00 1970 +0000
83 date: Thu Jan 01 00:00:00 1970 +0000
84 summary: one file
84 summary: one file
85
85
86 $ hg cat -r tip a
86 $ hg cat -r tip a
87 a
87 a
88 $ cat a
88 $ cat a
89 a
89 a
90 a
90 a
91
91
92 Committing only one hunk while aborting edition of hunk
92 Committing only one hunk while aborting edition of hunk
93
93
94 - Untoggle all the hunks, go down to the second file
94 - Untoggle all the hunks, go down to the second file
95 - unfold it
95 - unfold it
96 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
96 - go down to second hunk (1 for the first hunk, 1 for the first hunkline, 1 for the second hunk, 1 for the second hunklike)
97 - toggle the second hunk
97 - toggle the second hunk
98 - toggle on and off the amend mode (to check that it toggles off)
98 - toggle on and off the amend mode (to check that it toggles off)
99 - edit the hunk and quit the editor immediately with non-zero status
99 - edit the hunk and quit the editor immediately with non-zero status
100 - commit
100 - commit
101
101
102 $ printf "printf 'editor ran\n'; exit 1" > editor.sh
102 $ printf "printf 'editor ran\n'; exit 1" > editor.sh
103 $ echo "x" > c
103 $ echo "x" > c
104 $ cat b >> c
104 $ cat b >> c
105 $ echo "y" >> c
105 $ echo "y" >> c
106 $ mv c b
106 $ mv c b
107 $ cat <<EOF >testModeCommands
107 $ cat <<EOF >testModeCommands
108 > A
108 > A
109 > KEY_DOWN
109 > KEY_DOWN
110 > f
110 > f
111 > KEY_DOWN
111 > KEY_DOWN
112 > KEY_DOWN
112 > KEY_DOWN
113 > KEY_DOWN
113 > KEY_DOWN
114 > KEY_DOWN
114 > KEY_DOWN
115 > TOGGLE
115 > TOGGLE
116 > a
116 > a
117 > a
117 > a
118 > e
118 > e
119 > X
119 > X
120 > EOF
120 > EOF
121 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0"
121 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "one hunk" -d "0 0"
122 editor ran
122 editor ran
123 $ rm editor.sh
123 $ rm editor.sh
124 $ hg tip
124 $ hg tip
125 changeset: 2:7d10dfe755a8
125 changeset: 2:7d10dfe755a8
126 tag: tip
126 tag: tip
127 user: test
127 user: test
128 date: Thu Jan 01 00:00:00 1970 +0000
128 date: Thu Jan 01 00:00:00 1970 +0000
129 summary: one hunk
129 summary: one hunk
130
130
131 $ hg cat -r tip b
131 $ hg cat -r tip b
132 1
132 1
133 2
133 2
134 3
134 3
135 4
135 4
136 5
136 5
137 6
137 6
138 7
138 7
139 8
139 8
140 9
140 9
141 10
141 10
142 y
142 y
143 $ cat b
143 $ cat b
144 x
144 x
145 1
145 1
146 2
146 2
147 3
147 3
148 4
148 4
149 5
149 5
150 6
150 6
151 7
151 7
152 8
152 8
153 9
153 9
154 10
154 10
155 y
155 y
156 $ hg commit -m "other hunks"
156 $ hg commit -m "other hunks"
157 $ hg tip
157 $ hg tip
158 changeset: 3:a6735021574d
158 changeset: 3:a6735021574d
159 tag: tip
159 tag: tip
160 user: test
160 user: test
161 date: Thu Jan 01 00:00:00 1970 +0000
161 date: Thu Jan 01 00:00:00 1970 +0000
162 summary: other hunks
162 summary: other hunks
163
163
164 $ hg cat -r tip b
164 $ hg cat -r tip b
165 x
165 x
166 1
166 1
167 2
167 2
168 3
168 3
169 4
169 4
170 5
170 5
171 6
171 6
172 7
172 7
173 8
173 8
174 9
174 9
175 10
175 10
176 y
176 y
177
177
178 Newly added files can be selected with the curses interface
178 Newly added files can be selected with the curses interface
179
179
180 $ hg update -C .
180 $ hg update -C .
181 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
181 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
182 $ echo "hello" > x
182 $ echo "hello" > x
183 $ hg add x
183 $ hg add x
184 $ cat <<EOF >testModeCommands
184 $ cat <<EOF >testModeCommands
185 > TOGGLE
185 > TOGGLE
186 > TOGGLE
186 > TOGGLE
187 > X
187 > X
188 > EOF
188 > EOF
189 $ hg st
189 $ hg st
190 A x
190 A x
191 ? testModeCommands
191 ? testModeCommands
192 $ hg commit -i -m "newly added file" -d "0 0"
192 $ hg commit -i -m "newly added file" -d "0 0"
193 $ hg st
193 $ hg st
194 ? testModeCommands
194 ? testModeCommands
195
195
196 Amend option works
196 Amend option works
197 $ echo "hello world" > x
197 $ echo "hello world" > x
198 $ hg diff -c .
198 $ hg diff -c .
199 diff -r a6735021574d -r 2b0e9be4d336 x
199 diff -r a6735021574d -r 2b0e9be4d336 x
200 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
200 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
201 +++ b/x Thu Jan 01 00:00:00 1970 +0000
201 +++ b/x Thu Jan 01 00:00:00 1970 +0000
202 @@ -0,0 +1,1 @@
202 @@ -0,0 +1,1 @@
203 +hello
203 +hello
204 $ cat <<EOF >testModeCommands
204 $ cat <<EOF >testModeCommands
205 > a
205 > a
206 > X
206 > X
207 > EOF
207 > EOF
208 $ hg commit -i -m "newly added file" -d "0 0"
208 $ hg commit -i -m "newly added file" -d "0 0"
209 saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg
209 saved backup bundle to $TESTTMP/a/.hg/strip-backup/2b0e9be4d336-3cf0bc8c-amend.hg
210 $ hg diff -c .
210 $ hg diff -c .
211 diff -r a6735021574d -r c1d239d165ae x
211 diff -r a6735021574d -r c1d239d165ae x
212 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
212 --- /dev/null Thu Jan 01 00:00:00 1970 +0000
213 +++ b/x Thu Jan 01 00:00:00 1970 +0000
213 +++ b/x Thu Jan 01 00:00:00 1970 +0000
214 @@ -0,0 +1,1 @@
214 @@ -0,0 +1,1 @@
215 +hello world
215 +hello world
216
216
217 Make file empty
218 $ printf "" > x
219 $ cat <<EOF >testModeCommands
220 > X
221 > EOF
222 $ hg ci -i -m emptify -d "0 0"
223 $ hg update -C '.^' -q
224
217 Editing a hunk puts you back on that hunk when done editing (issue5041)
225 Editing a hunk puts you back on that hunk when done editing (issue5041)
218 To do that, we change two lines in a file, pretend to edit the second line,
226 To do that, we change two lines in a file, pretend to edit the second line,
219 exit, toggle the line selected at the end of the edit and commit.
227 exit, toggle the line selected at the end of the edit and commit.
220 The first line should be recorded if we were put on the second line at the end
228 The first line should be recorded if we were put on the second line at the end
221 of the edit.
229 of the edit.
222
230
223 $ hg update -C .
231 $ hg update -C .
224 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
232 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
225 $ echo "foo" > x
233 $ echo "foo" > x
226 $ echo "hello world" >> x
234 $ echo "hello world" >> x
227 $ echo "bar" >> x
235 $ echo "bar" >> x
228 $ cat <<EOF >testModeCommands
236 $ cat <<EOF >testModeCommands
229 > f
237 > f
230 > KEY_DOWN
238 > KEY_DOWN
231 > KEY_DOWN
239 > KEY_DOWN
232 > KEY_DOWN
240 > KEY_DOWN
233 > KEY_DOWN
241 > KEY_DOWN
234 > e
242 > e
235 > TOGGLE
243 > TOGGLE
236 > X
244 > X
237 > EOF
245 > EOF
238 $ printf "printf 'editor ran\n'; exit 0" > editor.sh
246 $ printf "printf 'editor ran\n'; exit 0" > editor.sh
239 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "edit hunk" -d "0 0"
247 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "edit hunk" -d "0 0" -q
240 editor ran
248 editor ran
241 $ hg cat -r . x
249 $ hg cat -r . x
242 foo
250 foo
243 hello world
251 hello world
244
252
245 Testing the review option. The entire final filtered patch should show
253 Testing the review option. The entire final filtered patch should show
246 up in the editor and be editable. We will unselect the second file and
254 up in the editor and be editable. We will unselect the second file and
247 the first hunk of the third file. During review, we will decide that
255 the first hunk of the third file. During review, we will decide that
248 "lower" sounds better than "bottom", and the final commit should
256 "lower" sounds better than "bottom", and the final commit should
249 reflect this edition.
257 reflect this edition.
250
258
251 $ hg update -C .
259 $ hg update -C .
252 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
260 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
253 $ echo "top" > c
261 $ echo "top" > c
254 $ cat x >> c
262 $ cat x >> c
255 $ echo "bottom" >> c
263 $ echo "bottom" >> c
256 $ mv c x
264 $ mv c x
257 $ echo "third a" >> a
265 $ echo "third a" >> a
258 $ echo "we will unselect this" >> b
266 $ echo "we will unselect this" >> b
259
267
260 $ cat > editor.sh <<EOF
268 $ cat > editor.sh <<EOF
261 > cat "\$1"
269 > cat "\$1"
262 > cat "\$1" | sed s/bottom/lower/ > tmp
270 > cat "\$1" | sed s/bottom/lower/ > tmp
263 > mv tmp "\$1"
271 > mv tmp "\$1"
264 > EOF
272 > EOF
265 $ cat > testModeCommands <<EOF
273 $ cat > testModeCommands <<EOF
266 > KEY_DOWN
274 > KEY_DOWN
267 > TOGGLE
275 > TOGGLE
268 > KEY_DOWN
276 > KEY_DOWN
269 > f
277 > f
270 > KEY_DOWN
278 > KEY_DOWN
271 > TOGGLE
279 > TOGGLE
272 > R
280 > R
273 > EOF
281 > EOF
274
282
275 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "review hunks" -d "0 0"
283 $ HGEDITOR="\"sh\" \"`pwd`/editor.sh\"" hg commit -i -m "review hunks" -d "0 0"
276 # To remove '-' lines, make them ' ' lines (context).
284 # To remove '-' lines, make them ' ' lines (context).
277 # To remove '+' lines, delete them.
285 # To remove '+' lines, delete them.
278 # Lines starting with # will be removed from the patch.
286 # Lines starting with # will be removed from the patch.
279 #
287 #
280 # If the patch applies cleanly, the edited patch will immediately
288 # If the patch applies cleanly, the edited patch will immediately
281 # be finalised. If it does not apply cleanly, rejects files will be
289 # be finalised. If it does not apply cleanly, rejects files will be
282 # generated. You can use those when you try again.
290 # generated. You can use those when you try again.
283 diff --git a/a b/a
291 diff --git a/a b/a
284 --- a/a
292 --- a/a
285 +++ b/a
293 +++ b/a
286 @@ -1,2 +1,3 @@
294 @@ -1,2 +1,3 @@
287 a
295 a
288 a
296 a
289 +third a
297 +third a
290 diff --git a/x b/x
298 diff --git a/x b/x
291 --- a/x
299 --- a/x
292 +++ b/x
300 +++ b/x
293 @@ -1,2 +1,3 @@
301 @@ -1,2 +1,3 @@
294 foo
302 foo
295 hello world
303 hello world
296 +bottom
304 +bottom
297
305
298 $ hg cat -r . a
306 $ hg cat -r . a
299 a
307 a
300 a
308 a
301 third a
309 third a
302
310
303 $ hg cat -r . b
311 $ hg cat -r . b
304 x
312 x
305 1
313 1
306 2
314 2
307 3
315 3
308 4
316 4
309 5
317 5
310 6
318 6
311 7
319 7
312 8
320 8
313 9
321 9
314 10
322 10
315 y
323 y
316
324
317 $ hg cat -r . x
325 $ hg cat -r . x
318 foo
326 foo
319 hello world
327 hello world
320 lower
328 lower
321
329
322 Check spacemovesdown
330 Check spacemovesdown
323
331
324 $ cat <<EOF >> $HGRCPATH
332 $ cat <<EOF >> $HGRCPATH
325 > [experimental]
333 > [experimental]
326 > spacemovesdown = true
334 > spacemovesdown = true
327 > EOF
335 > EOF
328 $ cat <<EOF >testModeCommands
336 $ cat <<EOF >testModeCommands
329 > TOGGLE
337 > TOGGLE
330 > TOGGLE
338 > TOGGLE
331 > X
339 > X
332 > EOF
340 > EOF
333 $ hg status -q
341 $ hg status -q
334 M b
342 M b
335 M x
343 M x
336 $ hg commit -i -m "nothing to commit?" -d "0 0"
344 $ hg commit -i -m "nothing to commit?" -d "0 0"
337 no changes to record
345 no changes to record
338 [1]
346 [1]
339
347
340 Check ui.interface logic for the chunkselector
348 Check ui.interface logic for the chunkselector
341
349
342 The default interface is text
350 The default interface is text
343 $ cp $HGRCPATH.pretest $HGRCPATH
351 $ cp $HGRCPATH.pretest $HGRCPATH
344 $ chunkselectorinterface() {
352 $ chunkselectorinterface() {
345 > $PYTHON <<EOF
353 > $PYTHON <<EOF
346 > from mercurial import hg, ui;\
354 > from mercurial import hg, ui;\
347 > repo = hg.repository(ui.ui.load(), ".");\
355 > repo = hg.repository(ui.ui.load(), ".");\
348 > print(repo.ui.interface("chunkselector"))
356 > print(repo.ui.interface("chunkselector"))
349 > EOF
357 > EOF
350 > }
358 > }
351 $ chunkselectorinterface
359 $ chunkselectorinterface
352 text
360 text
353
361
354 If only the default is set, we'll use that for the feature, too
362 If only the default is set, we'll use that for the feature, too
355 $ cp $HGRCPATH.pretest $HGRCPATH
363 $ cp $HGRCPATH.pretest $HGRCPATH
356 $ cat <<EOF >> $HGRCPATH
364 $ cat <<EOF >> $HGRCPATH
357 > [ui]
365 > [ui]
358 > interface = curses
366 > interface = curses
359 > EOF
367 > EOF
360 $ chunkselectorinterface
368 $ chunkselectorinterface
361 curses
369 curses
362
370
363 It is possible to override the default interface with a feature specific
371 It is possible to override the default interface with a feature specific
364 interface
372 interface
365 $ cp $HGRCPATH.pretest $HGRCPATH
373 $ cp $HGRCPATH.pretest $HGRCPATH
366 $ cat <<EOF >> $HGRCPATH
374 $ cat <<EOF >> $HGRCPATH
367 > [ui]
375 > [ui]
368 > interface = text
376 > interface = text
369 > interface.chunkselector = curses
377 > interface.chunkselector = curses
370 > EOF
378 > EOF
371
379
372 $ chunkselectorinterface
380 $ chunkselectorinterface
373 curses
381 curses
374
382
375 $ cp $HGRCPATH.pretest $HGRCPATH
383 $ cp $HGRCPATH.pretest $HGRCPATH
376 $ cat <<EOF >> $HGRCPATH
384 $ cat <<EOF >> $HGRCPATH
377 > [ui]
385 > [ui]
378 > interface = curses
386 > interface = curses
379 > interface.chunkselector = text
387 > interface.chunkselector = text
380 > EOF
388 > EOF
381
389
382 $ chunkselectorinterface
390 $ chunkselectorinterface
383 text
391 text
384
392
385 If a bad interface name is given, we use the default value (with a nice
393 If a bad interface name is given, we use the default value (with a nice
386 error message to suggest that the configuration needs to be fixed)
394 error message to suggest that the configuration needs to be fixed)
387
395
388 $ cp $HGRCPATH.pretest $HGRCPATH
396 $ cp $HGRCPATH.pretest $HGRCPATH
389 $ cat <<EOF >> $HGRCPATH
397 $ cat <<EOF >> $HGRCPATH
390 > [ui]
398 > [ui]
391 > interface = blah
399 > interface = blah
392 > EOF
400 > EOF
393 $ chunkselectorinterface
401 $ chunkselectorinterface
394 invalid value for ui.interface: blah (using text)
402 invalid value for ui.interface: blah (using text)
395 text
403 text
396
404
397 $ cp $HGRCPATH.pretest $HGRCPATH
405 $ cp $HGRCPATH.pretest $HGRCPATH
398 $ cat <<EOF >> $HGRCPATH
406 $ cat <<EOF >> $HGRCPATH
399 > [ui]
407 > [ui]
400 > interface = curses
408 > interface = curses
401 > interface.chunkselector = blah
409 > interface.chunkselector = blah
402 > EOF
410 > EOF
403 $ chunkselectorinterface
411 $ chunkselectorinterface
404 invalid value for ui.interface.chunkselector: blah (using curses)
412 invalid value for ui.interface.chunkselector: blah (using curses)
405 curses
413 curses
406
414
407 $ cp $HGRCPATH.pretest $HGRCPATH
415 $ cp $HGRCPATH.pretest $HGRCPATH
408 $ cat <<EOF >> $HGRCPATH
416 $ cat <<EOF >> $HGRCPATH
409 > [ui]
417 > [ui]
410 > interface = blah
418 > interface = blah
411 > interface.chunkselector = curses
419 > interface.chunkselector = curses
412 > EOF
420 > EOF
413 $ chunkselectorinterface
421 $ chunkselectorinterface
414 invalid value for ui.interface: blah
422 invalid value for ui.interface: blah
415 curses
423 curses
416
424
417 $ cp $HGRCPATH.pretest $HGRCPATH
425 $ cp $HGRCPATH.pretest $HGRCPATH
418 $ cat <<EOF >> $HGRCPATH
426 $ cat <<EOF >> $HGRCPATH
419 > [ui]
427 > [ui]
420 > interface = blah
428 > interface = blah
421 > interface.chunkselector = blah
429 > interface.chunkselector = blah
422 > EOF
430 > EOF
423 $ chunkselectorinterface
431 $ chunkselectorinterface
424 invalid value for ui.interface: blah
432 invalid value for ui.interface: blah
425 invalid value for ui.interface.chunkselector: blah (using text)
433 invalid value for ui.interface.chunkselector: blah (using text)
426 text
434 text
@@ -1,1943 +1,1983 b''
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
1 Let commit recurse into subrepos by default to match pre-2.0 behavior:
2
2
3 $ echo "[ui]" >> $HGRCPATH
3 $ echo "[ui]" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
4 $ echo "commitsubrepos = Yes" >> $HGRCPATH
5
5
6 $ hg init t
6 $ hg init t
7 $ cd t
7 $ cd t
8
8
9 first revision, no sub
9 first revision, no sub
10
10
11 $ echo a > a
11 $ echo a > a
12 $ hg ci -Am0
12 $ hg ci -Am0
13 adding a
13 adding a
14
14
15 add first sub
15 add first sub
16
16
17 $ echo s = s > .hgsub
17 $ echo s = s > .hgsub
18 $ hg add .hgsub
18 $ hg add .hgsub
19 $ hg init s
19 $ hg init s
20 $ echo a > s/a
20 $ echo a > s/a
21
21
22 Issue2232: committing a subrepo without .hgsub
22 Issue2232: committing a subrepo without .hgsub
23
23
24 $ hg ci -mbad s
24 $ hg ci -mbad s
25 abort: can't commit subrepos without .hgsub
25 abort: can't commit subrepos without .hgsub
26 [255]
26 [255]
27
27
28 $ hg -R s add s/a
28 $ hg -R s add s/a
29 $ hg files -S
29 $ hg files -S
30 .hgsub
30 .hgsub
31 a
31 a
32 s/a
32 s/a
33
33
34 $ hg -R s ci -Ams0
34 $ hg -R s ci -Ams0
35 $ hg sum
35 $ hg sum
36 parent: 0:f7b1eb17ad24 tip
36 parent: 0:f7b1eb17ad24 tip
37 0
37 0
38 branch: default
38 branch: default
39 commit: 1 added, 1 subrepos
39 commit: 1 added, 1 subrepos
40 update: (current)
40 update: (current)
41 phases: 1 draft
41 phases: 1 draft
42 $ hg ci -m1
42 $ hg ci -m1
43
43
44 test handling .hgsubstate "added" explicitly.
44 test handling .hgsubstate "added" explicitly.
45
45
46 $ hg parents --template '{node}\n{files}\n'
46 $ hg parents --template '{node}\n{files}\n'
47 7cf8cfea66e410e8e3336508dfeec07b3192de51
47 7cf8cfea66e410e8e3336508dfeec07b3192de51
48 .hgsub .hgsubstate
48 .hgsub .hgsubstate
49 $ hg rollback -q
49 $ hg rollback -q
50 $ hg add .hgsubstate
50 $ hg add .hgsubstate
51 $ hg ci -m1
51 $ hg ci -m1
52 $ hg parents --template '{node}\n{files}\n'
52 $ hg parents --template '{node}\n{files}\n'
53 7cf8cfea66e410e8e3336508dfeec07b3192de51
53 7cf8cfea66e410e8e3336508dfeec07b3192de51
54 .hgsub .hgsubstate
54 .hgsub .hgsubstate
55
55
56 Subrepopath which overlaps with filepath, does not change warnings in remove()
56 Subrepopath which overlaps with filepath, does not change warnings in remove()
57
57
58 $ mkdir snot
58 $ mkdir snot
59 $ touch snot/file
59 $ touch snot/file
60 $ hg remove -S snot/file
60 $ hg remove -S snot/file
61 not removing snot/file: file is untracked
61 not removing snot/file: file is untracked
62 [1]
62 [1]
63 $ hg cat snot/filenot
63 $ hg cat snot/filenot
64 snot/filenot: no such file in rev 7cf8cfea66e4
64 snot/filenot: no such file in rev 7cf8cfea66e4
65 [1]
65 [1]
66 $ rm -r snot
66 $ rm -r snot
67
67
68 Revert subrepo and test subrepo fileset keyword:
68 Revert subrepo and test subrepo fileset keyword:
69
69
70 $ echo b > s/a
70 $ echo b > s/a
71 $ hg revert --dry-run "set:subrepo('glob:s*')"
71 $ hg revert --dry-run "set:subrepo('glob:s*')"
72 reverting subrepo s
72 reverting subrepo s
73 reverting s/a
73 reverting s/a
74 $ cat s/a
74 $ cat s/a
75 b
75 b
76 $ hg revert "set:subrepo('glob:s*')"
76 $ hg revert "set:subrepo('glob:s*')"
77 reverting subrepo s
77 reverting subrepo s
78 reverting s/a
78 reverting s/a
79 $ cat s/a
79 $ cat s/a
80 a
80 a
81 $ rm s/a.orig
81 $ rm s/a.orig
82
82
83 Revert subrepo with no backup. The "reverting s/a" line is gone since
83 Revert subrepo with no backup. The "reverting s/a" line is gone since
84 we're really running 'hg update' in the subrepo:
84 we're really running 'hg update' in the subrepo:
85
85
86 $ echo b > s/a
86 $ echo b > s/a
87 $ hg revert --no-backup s
87 $ hg revert --no-backup s
88 reverting subrepo s
88 reverting subrepo s
89
89
90 Issue2022: update -C
90 Issue2022: update -C
91
91
92 $ echo b > s/a
92 $ echo b > s/a
93 $ hg sum
93 $ hg sum
94 parent: 1:7cf8cfea66e4 tip
94 parent: 1:7cf8cfea66e4 tip
95 1
95 1
96 branch: default
96 branch: default
97 commit: 1 subrepos
97 commit: 1 subrepos
98 update: (current)
98 update: (current)
99 phases: 2 draft
99 phases: 2 draft
100 $ hg co -C 1
100 $ hg co -C 1
101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
101 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
102 $ hg sum
102 $ hg sum
103 parent: 1:7cf8cfea66e4 tip
103 parent: 1:7cf8cfea66e4 tip
104 1
104 1
105 branch: default
105 branch: default
106 commit: (clean)
106 commit: (clean)
107 update: (current)
107 update: (current)
108 phases: 2 draft
108 phases: 2 draft
109
109
110 commands that require a clean repo should respect subrepos
110 commands that require a clean repo should respect subrepos
111
111
112 $ echo b >> s/a
112 $ echo b >> s/a
113 $ hg backout tip
113 $ hg backout tip
114 abort: uncommitted changes in subrepository "s"
114 abort: uncommitted changes in subrepository "s"
115 [255]
115 [255]
116 $ hg revert -C -R s s/a
116 $ hg revert -C -R s s/a
117
117
118 add sub sub
118 add sub sub
119
119
120 $ echo ss = ss > s/.hgsub
120 $ echo ss = ss > s/.hgsub
121 $ hg init s/ss
121 $ hg init s/ss
122 $ echo a > s/ss/a
122 $ echo a > s/ss/a
123 $ hg -R s add s/.hgsub
123 $ hg -R s add s/.hgsub
124 $ hg -R s/ss add s/ss/a
124 $ hg -R s/ss add s/ss/a
125 $ hg sum
125 $ hg sum
126 parent: 1:7cf8cfea66e4 tip
126 parent: 1:7cf8cfea66e4 tip
127 1
127 1
128 branch: default
128 branch: default
129 commit: 1 subrepos
129 commit: 1 subrepos
130 update: (current)
130 update: (current)
131 phases: 2 draft
131 phases: 2 draft
132 $ hg ci -m2
132 $ hg ci -m2
133 committing subrepository s
133 committing subrepository s
134 committing subrepository s/ss
134 committing subrepository s/ss
135 $ hg sum
135 $ hg sum
136 parent: 2:df30734270ae tip
136 parent: 2:df30734270ae tip
137 2
137 2
138 branch: default
138 branch: default
139 commit: (clean)
139 commit: (clean)
140 update: (current)
140 update: (current)
141 phases: 3 draft
141 phases: 3 draft
142
142
143 test handling .hgsubstate "modified" explicitly.
143 test handling .hgsubstate "modified" explicitly.
144
144
145 $ hg parents --template '{node}\n{files}\n'
145 $ hg parents --template '{node}\n{files}\n'
146 df30734270ae757feb35e643b7018e818e78a9aa
146 df30734270ae757feb35e643b7018e818e78a9aa
147 .hgsubstate
147 .hgsubstate
148 $ hg rollback -q
148 $ hg rollback -q
149 $ hg status -A .hgsubstate
149 $ hg status -A .hgsubstate
150 M .hgsubstate
150 M .hgsubstate
151 $ hg ci -m2
151 $ hg ci -m2
152 $ hg parents --template '{node}\n{files}\n'
152 $ hg parents --template '{node}\n{files}\n'
153 df30734270ae757feb35e643b7018e818e78a9aa
153 df30734270ae757feb35e643b7018e818e78a9aa
154 .hgsubstate
154 .hgsubstate
155
155
156 bump sub rev (and check it is ignored by ui.commitsubrepos)
156 bump sub rev (and check it is ignored by ui.commitsubrepos)
157
157
158 $ echo b > s/a
158 $ echo b > s/a
159 $ hg -R s ci -ms1
159 $ hg -R s ci -ms1
160 $ hg --config ui.commitsubrepos=no ci -m3
160 $ hg --config ui.commitsubrepos=no ci -m3
161
161
162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
162 leave sub dirty (and check ui.commitsubrepos=no aborts the commit)
163
163
164 $ echo c > s/a
164 $ echo c > s/a
165 $ hg --config ui.commitsubrepos=no ci -m4
165 $ hg --config ui.commitsubrepos=no ci -m4
166 abort: uncommitted changes in subrepository "s"
166 abort: uncommitted changes in subrepository "s"
167 (use --subrepos for recursive commit)
167 (use --subrepos for recursive commit)
168 [255]
168 [255]
169 $ hg id
169 $ hg id
170 f6affe3fbfaa+ tip
170 f6affe3fbfaa+ tip
171 $ hg -R s ci -mc
171 $ hg -R s ci -mc
172 $ hg id
172 $ hg id
173 f6affe3fbfaa+ tip
173 f6affe3fbfaa+ tip
174 $ echo d > s/a
174 $ echo d > s/a
175 $ hg ci -m4
175 $ hg ci -m4
176 committing subrepository s
176 committing subrepository s
177 $ hg tip -R s
177 $ hg tip -R s
178 changeset: 4:02dcf1d70411
178 changeset: 4:02dcf1d70411
179 tag: tip
179 tag: tip
180 user: test
180 user: test
181 date: Thu Jan 01 00:00:00 1970 +0000
181 date: Thu Jan 01 00:00:00 1970 +0000
182 summary: 4
182 summary: 4
183
183
184
184
185 check caching
185 check caching
186
186
187 $ hg co 0
187 $ hg co 0
188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
188 0 files updated, 0 files merged, 2 files removed, 0 files unresolved
189 $ hg debugsub
189 $ hg debugsub
190
190
191 restore
191 restore
192
192
193 $ hg co
193 $ hg co
194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
194 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
195 $ hg debugsub
195 $ hg debugsub
196 path s
196 path s
197 source s
197 source s
198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
198 revision 02dcf1d704118aee3ee306ccfa1910850d5b05ef
199
199
200 new branch for merge tests
200 new branch for merge tests
201
201
202 $ hg co 1
202 $ hg co 1
203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
203 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
204 $ echo t = t >> .hgsub
204 $ echo t = t >> .hgsub
205 $ hg init t
205 $ hg init t
206 $ echo t > t/t
206 $ echo t > t/t
207 $ hg -R t add t
207 $ hg -R t add t
208 adding t/t
208 adding t/t
209
209
210 5
210 5
211
211
212 $ hg ci -m5 # add sub
212 $ hg ci -m5 # add sub
213 committing subrepository t
213 committing subrepository t
214 created new head
214 created new head
215 $ echo t2 > t/t
215 $ echo t2 > t/t
216
216
217 6
217 6
218
218
219 $ hg st -R s
219 $ hg st -R s
220 $ hg ci -m6 # change sub
220 $ hg ci -m6 # change sub
221 committing subrepository t
221 committing subrepository t
222 $ hg debugsub
222 $ hg debugsub
223 path s
223 path s
224 source s
224 source s
225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
225 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
226 path t
226 path t
227 source t
227 source t
228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
228 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
229 $ echo t3 > t/t
229 $ echo t3 > t/t
230
230
231 7
231 7
232
232
233 $ hg ci -m7 # change sub again for conflict test
233 $ hg ci -m7 # change sub again for conflict test
234 committing subrepository t
234 committing subrepository t
235 $ hg rm .hgsub
235 $ hg rm .hgsub
236
236
237 8
237 8
238
238
239 $ hg ci -m8 # remove sub
239 $ hg ci -m8 # remove sub
240
240
241 test handling .hgsubstate "removed" explicitly.
241 test handling .hgsubstate "removed" explicitly.
242
242
243 $ hg parents --template '{node}\n{files}\n'
243 $ hg parents --template '{node}\n{files}\n'
244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
244 96615c1dad2dc8e3796d7332c77ce69156f7b78e
245 .hgsub .hgsubstate
245 .hgsub .hgsubstate
246 $ hg rollback -q
246 $ hg rollback -q
247 $ hg remove .hgsubstate
247 $ hg remove .hgsubstate
248 $ hg ci -m8
248 $ hg ci -m8
249 $ hg parents --template '{node}\n{files}\n'
249 $ hg parents --template '{node}\n{files}\n'
250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
250 96615c1dad2dc8e3796d7332c77ce69156f7b78e
251 .hgsub .hgsubstate
251 .hgsub .hgsubstate
252
252
253 merge tests
253 merge tests
254
254
255 $ hg co -C 3
255 $ hg co -C 3
256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
256 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
257 $ hg merge 5 # test adding
257 $ hg merge 5 # test adding
258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
258 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
259 (branch merge, don't forget to commit)
259 (branch merge, don't forget to commit)
260 $ hg debugsub
260 $ hg debugsub
261 path s
261 path s
262 source s
262 source s
263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
263 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
264 path t
264 path t
265 source t
265 source t
266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
266 revision 60ca1237c19474e7a3978b0dc1ca4e6f36d51382
267 $ hg ci -m9
267 $ hg ci -m9
268 created new head
268 created new head
269 $ hg merge 6 --debug # test change
269 $ hg merge 6 --debug # test change
270 searching for copies back to rev 2
270 searching for copies back to rev 2
271 resolving manifests
271 resolving manifests
272 branchmerge: True, force: False, partial: False
272 branchmerge: True, force: False, partial: False
273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
273 ancestor: 1f14a2e2d3ec, local: f0d2028bf86d+, remote: 1831e14459c4
274 starting 4 threads for background file closing (?)
274 starting 4 threads for background file closing (?)
275 .hgsubstate: versions differ -> m (premerge)
275 .hgsubstate: versions differ -> m (premerge)
276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
276 subrepo merge f0d2028bf86d+ 1831e14459c4 1f14a2e2d3ec
277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
277 subrepo t: other changed, get t:6747d179aa9a688023c4b0cad32e4c92bb7f34ad:hg
278 getting subrepo t
278 getting subrepo t
279 resolving manifests
279 resolving manifests
280 branchmerge: False, force: False, partial: False
280 branchmerge: False, force: False, partial: False
281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
281 ancestor: 60ca1237c194, local: 60ca1237c194+, remote: 6747d179aa9a
282 t: remote is newer -> g
282 t: remote is newer -> g
283 getting t
283 getting t
284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
284 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
285 (branch merge, don't forget to commit)
285 (branch merge, don't forget to commit)
286 $ hg debugsub
286 $ hg debugsub
287 path s
287 path s
288 source s
288 source s
289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
289 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
290 path t
290 path t
291 source t
291 source t
292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
292 revision 6747d179aa9a688023c4b0cad32e4c92bb7f34ad
293 $ echo conflict > t/t
293 $ echo conflict > t/t
294 $ hg ci -m10
294 $ hg ci -m10
295 committing subrepository t
295 committing subrepository t
296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
296 $ HGMERGE=internal:merge hg merge --debug 7 # test conflict
297 searching for copies back to rev 2
297 searching for copies back to rev 2
298 resolving manifests
298 resolving manifests
299 branchmerge: True, force: False, partial: False
299 branchmerge: True, force: False, partial: False
300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
300 ancestor: 1831e14459c4, local: e45c8b14af55+, remote: f94576341bcf
301 starting 4 threads for background file closing (?)
301 starting 4 threads for background file closing (?)
302 .hgsubstate: versions differ -> m (premerge)
302 .hgsubstate: versions differ -> m (premerge)
303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
303 subrepo merge e45c8b14af55+ f94576341bcf 1831e14459c4
304 subrepo t: both sides changed
304 subrepo t: both sides changed
305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
305 subrepository t diverged (local revision: 20a0db6fbf6c, remote revision: 7af322bc1198)
306 starting 4 threads for background file closing (?)
306 starting 4 threads for background file closing (?)
307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
307 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
308 merging subrepository "t"
308 merging subrepository "t"
309 searching for copies back to rev 2
309 searching for copies back to rev 2
310 resolving manifests
310 resolving manifests
311 branchmerge: True, force: False, partial: False
311 branchmerge: True, force: False, partial: False
312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
312 ancestor: 6747d179aa9a, local: 20a0db6fbf6c+, remote: 7af322bc1198
313 preserving t for resolve of t
313 preserving t for resolve of t
314 starting 4 threads for background file closing (?)
314 starting 4 threads for background file closing (?)
315 t: versions differ -> m (premerge)
315 t: versions differ -> m (premerge)
316 picked tool ':merge' for t (binary False symlink False changedelete False)
316 picked tool ':merge' for t (binary False symlink False changedelete False)
317 merging t
317 merging t
318 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
318 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
319 t: versions differ -> m (merge)
319 t: versions differ -> m (merge)
320 picked tool ':merge' for t (binary False symlink False changedelete False)
320 picked tool ':merge' for t (binary False symlink False changedelete False)
321 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
321 my t@20a0db6fbf6c+ other t@7af322bc1198 ancestor t@6747d179aa9a
322 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
322 warning: conflicts while merging t! (edit, then use 'hg resolve --mark')
323 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
323 0 files updated, 0 files merged, 0 files removed, 1 files unresolved
324 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
324 use 'hg resolve' to retry unresolved file merges or 'hg merge --abort' to abandon
325 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
325 subrepo t: merge with t:7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4:hg
326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
326 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
327 (branch merge, don't forget to commit)
327 (branch merge, don't forget to commit)
328
328
329 should conflict
329 should conflict
330
330
331 $ cat t/t
331 $ cat t/t
332 <<<<<<< local: 20a0db6fbf6c - test: 10
332 <<<<<<< local: 20a0db6fbf6c - test: 10
333 conflict
333 conflict
334 =======
334 =======
335 t3
335 t3
336 >>>>>>> other: 7af322bc1198 - test: 7
336 >>>>>>> other: 7af322bc1198 - test: 7
337
337
338 11: remove subrepo t
338 11: remove subrepo t
339
339
340 $ hg co -C 5
340 $ hg co -C 5
341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
341 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
342 $ hg revert -r 4 .hgsub # remove t
342 $ hg revert -r 4 .hgsub # remove t
343 $ hg ci -m11
343 $ hg ci -m11
344 created new head
344 created new head
345 $ hg debugsub
345 $ hg debugsub
346 path s
346 path s
347 source s
347 source s
348 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
348 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
349
349
350 local removed, remote changed, keep changed
350 local removed, remote changed, keep changed
351
351
352 $ hg merge 6
352 $ hg merge 6
353 remote [merge rev] changed subrepository t which local [working copy] removed
353 remote [merge rev] changed subrepository t which local [working copy] removed
354 use (c)hanged version or (d)elete? c
354 use (c)hanged version or (d)elete? c
355 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
355 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
356 (branch merge, don't forget to commit)
356 (branch merge, don't forget to commit)
357 BROKEN: should include subrepo t
357 BROKEN: should include subrepo t
358 $ hg debugsub
358 $ hg debugsub
359 path s
359 path s
360 source s
360 source s
361 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
361 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
362 $ cat .hgsubstate
362 $ cat .hgsubstate
363 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
363 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
364 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
364 6747d179aa9a688023c4b0cad32e4c92bb7f34ad t
365 $ hg ci -m 'local removed, remote changed, keep changed'
365 $ hg ci -m 'local removed, remote changed, keep changed'
366 BROKEN: should include subrepo t
366 BROKEN: should include subrepo t
367 $ hg debugsub
367 $ hg debugsub
368 path s
368 path s
369 source s
369 source s
370 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
370 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
371 BROKEN: should include subrepo t
371 BROKEN: should include subrepo t
372 $ cat .hgsubstate
372 $ cat .hgsubstate
373 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
373 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
374 $ cat t/t
374 $ cat t/t
375 t2
375 t2
376
376
377 local removed, remote changed, keep removed
377 local removed, remote changed, keep removed
378
378
379 $ hg co -C 11
379 $ hg co -C 11
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
380 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
381 $ hg merge --config ui.interactive=true 6 <<EOF
381 $ hg merge --config ui.interactive=true 6 <<EOF
382 > d
382 > d
383 > EOF
383 > EOF
384 remote [merge rev] changed subrepository t which local [working copy] removed
384 remote [merge rev] changed subrepository t which local [working copy] removed
385 use (c)hanged version or (d)elete? d
385 use (c)hanged version or (d)elete? d
386 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
386 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
387 (branch merge, don't forget to commit)
387 (branch merge, don't forget to commit)
388 $ hg debugsub
388 $ hg debugsub
389 path s
389 path s
390 source s
390 source s
391 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
391 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
392 $ cat .hgsubstate
392 $ cat .hgsubstate
393 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
393 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
394 $ hg ci -m 'local removed, remote changed, keep removed'
394 $ hg ci -m 'local removed, remote changed, keep removed'
395 created new head
395 created new head
396 $ hg debugsub
396 $ hg debugsub
397 path s
397 path s
398 source s
398 source s
399 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
399 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
400 $ cat .hgsubstate
400 $ cat .hgsubstate
401 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
401 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
402
402
403 local changed, remote removed, keep changed
403 local changed, remote removed, keep changed
404
404
405 $ hg co -C 6
405 $ hg co -C 6
406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
406 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
407 $ hg merge 11
407 $ hg merge 11
408 local [working copy] changed subrepository t which remote [merge rev] removed
408 local [working copy] changed subrepository t which remote [merge rev] removed
409 use (c)hanged version or (d)elete? c
409 use (c)hanged version or (d)elete? c
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
410 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
411 (branch merge, don't forget to commit)
411 (branch merge, don't forget to commit)
412 BROKEN: should include subrepo t
412 BROKEN: should include subrepo t
413 $ hg debugsub
413 $ hg debugsub
414 path s
414 path s
415 source s
415 source s
416 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
416 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
417 BROKEN: should include subrepo t
417 BROKEN: should include subrepo t
418 $ cat .hgsubstate
418 $ cat .hgsubstate
419 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
419 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
420 $ hg ci -m 'local changed, remote removed, keep changed'
420 $ hg ci -m 'local changed, remote removed, keep changed'
421 created new head
421 created new head
422 BROKEN: should include subrepo t
422 BROKEN: should include subrepo t
423 $ hg debugsub
423 $ hg debugsub
424 path s
424 path s
425 source s
425 source s
426 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
426 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
427 BROKEN: should include subrepo t
427 BROKEN: should include subrepo t
428 $ cat .hgsubstate
428 $ cat .hgsubstate
429 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
429 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
430 $ cat t/t
430 $ cat t/t
431 t2
431 t2
432
432
433 local changed, remote removed, keep removed
433 local changed, remote removed, keep removed
434
434
435 $ hg co -C 6
435 $ hg co -C 6
436 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
436 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
437 $ hg merge --config ui.interactive=true 11 <<EOF
437 $ hg merge --config ui.interactive=true 11 <<EOF
438 > d
438 > d
439 > EOF
439 > EOF
440 local [working copy] changed subrepository t which remote [merge rev] removed
440 local [working copy] changed subrepository t which remote [merge rev] removed
441 use (c)hanged version or (d)elete? d
441 use (c)hanged version or (d)elete? d
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
442 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
443 (branch merge, don't forget to commit)
443 (branch merge, don't forget to commit)
444 $ hg debugsub
444 $ hg debugsub
445 path s
445 path s
446 source s
446 source s
447 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
447 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
448 $ cat .hgsubstate
448 $ cat .hgsubstate
449 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
449 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
450 $ hg ci -m 'local changed, remote removed, keep removed'
450 $ hg ci -m 'local changed, remote removed, keep removed'
451 created new head
451 created new head
452 $ hg debugsub
452 $ hg debugsub
453 path s
453 path s
454 source s
454 source s
455 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
455 revision e4ece1bf43360ddc8f6a96432201a37b7cd27ae4
456 $ cat .hgsubstate
456 $ cat .hgsubstate
457 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
457 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
458
458
459 clean up to avoid having to fix up the tests below
459 clean up to avoid having to fix up the tests below
460
460
461 $ hg co -C 10
461 $ hg co -C 10
462 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
462 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
463 $ cat >> $HGRCPATH <<EOF
463 $ cat >> $HGRCPATH <<EOF
464 > [extensions]
464 > [extensions]
465 > strip=
465 > strip=
466 > EOF
466 > EOF
467 $ hg strip -r 11:15
467 $ hg strip -r 11:15
468 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
468 saved backup bundle to $TESTTMP/t/.hg/strip-backup/*-backup.hg (glob)
469
469
470 clone
470 clone
471
471
472 $ cd ..
472 $ cd ..
473 $ hg clone t tc
473 $ hg clone t tc
474 updating to branch default
474 updating to branch default
475 cloning subrepo s from $TESTTMP/t/s
475 cloning subrepo s from $TESTTMP/t/s
476 cloning subrepo s/ss from $TESTTMP/t/s/ss
476 cloning subrepo s/ss from $TESTTMP/t/s/ss
477 cloning subrepo t from $TESTTMP/t/t
477 cloning subrepo t from $TESTTMP/t/t
478 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
478 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
479 $ cd tc
479 $ cd tc
480 $ hg debugsub
480 $ hg debugsub
481 path s
481 path s
482 source s
482 source s
483 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
483 revision fc627a69481fcbe5f1135069e8a3881c023e4cf5
484 path t
484 path t
485 source t
485 source t
486 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
486 revision 20a0db6fbf6c3d2836e6519a642ae929bfc67c0e
487 $ cd ..
487 $ cd ..
488
488
489 clone with subrepo disabled (update should fail)
489 clone with subrepo disabled (update should fail)
490
490
491 $ hg clone t -U tc2 --config subrepos.allowed=false
491 $ hg clone t -U tc2 --config subrepos.allowed=false
492 $ hg update -R tc2 --config subrepos.allowed=false
492 $ hg update -R tc2 --config subrepos.allowed=false
493 abort: subrepos not enabled
493 abort: subrepos not enabled
494 (see 'hg help config.subrepos' for details)
494 (see 'hg help config.subrepos' for details)
495 [255]
495 [255]
496 $ ls tc2
496 $ ls tc2
497 a
497 a
498
498
499 $ hg clone t tc3 --config subrepos.allowed=false
499 $ hg clone t tc3 --config subrepos.allowed=false
500 updating to branch default
500 updating to branch default
501 abort: subrepos not enabled
501 abort: subrepos not enabled
502 (see 'hg help config.subrepos' for details)
502 (see 'hg help config.subrepos' for details)
503 [255]
503 [255]
504 $ ls tc3
504 $ ls tc3
505 a
505 a
506
506
507 And again with just the hg type disabled
507 And again with just the hg type disabled
508
508
509 $ hg clone t -U tc4 --config subrepos.hg:allowed=false
509 $ hg clone t -U tc4 --config subrepos.hg:allowed=false
510 $ hg update -R tc4 --config subrepos.hg:allowed=false
510 $ hg update -R tc4 --config subrepos.hg:allowed=false
511 abort: hg subrepos not allowed
511 abort: hg subrepos not allowed
512 (see 'hg help config.subrepos' for details)
512 (see 'hg help config.subrepos' for details)
513 [255]
513 [255]
514 $ ls tc4
514 $ ls tc4
515 a
515 a
516
516
517 $ hg clone t tc5 --config subrepos.hg:allowed=false
517 $ hg clone t tc5 --config subrepos.hg:allowed=false
518 updating to branch default
518 updating to branch default
519 abort: hg subrepos not allowed
519 abort: hg subrepos not allowed
520 (see 'hg help config.subrepos' for details)
520 (see 'hg help config.subrepos' for details)
521 [255]
521 [255]
522 $ ls tc5
522 $ ls tc5
523 a
523 a
524
524
525 push
525 push
526
526
527 $ cd tc
527 $ cd tc
528 $ echo bah > t/t
528 $ echo bah > t/t
529 $ hg ci -m11
529 $ hg ci -m11
530 committing subrepository t
530 committing subrepository t
531 $ hg push
531 $ hg push
532 pushing to $TESTTMP/t
532 pushing to $TESTTMP/t
533 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
533 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
534 no changes made to subrepo s since last push to $TESTTMP/t/s
534 no changes made to subrepo s since last push to $TESTTMP/t/s
535 pushing subrepo t to $TESTTMP/t/t
535 pushing subrepo t to $TESTTMP/t/t
536 searching for changes
536 searching for changes
537 adding changesets
537 adding changesets
538 adding manifests
538 adding manifests
539 adding file changes
539 adding file changes
540 added 1 changesets with 1 changes to 1 files
540 added 1 changesets with 1 changes to 1 files
541 searching for changes
541 searching for changes
542 adding changesets
542 adding changesets
543 adding manifests
543 adding manifests
544 adding file changes
544 adding file changes
545 added 1 changesets with 1 changes to 1 files
545 added 1 changesets with 1 changes to 1 files
546
546
547 push -f
547 push -f
548
548
549 $ echo bah > s/a
549 $ echo bah > s/a
550 $ hg ci -m12
550 $ hg ci -m12
551 committing subrepository s
551 committing subrepository s
552 $ hg push
552 $ hg push
553 pushing to $TESTTMP/t
553 pushing to $TESTTMP/t
554 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
554 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
555 pushing subrepo s to $TESTTMP/t/s
555 pushing subrepo s to $TESTTMP/t/s
556 searching for changes
556 searching for changes
557 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
557 abort: push creates new remote head 12a213df6fa9! (in subrepository "s")
558 (merge or see 'hg help push' for details about pushing new heads)
558 (merge or see 'hg help push' for details about pushing new heads)
559 [255]
559 [255]
560 $ hg push -f
560 $ hg push -f
561 pushing to $TESTTMP/t
561 pushing to $TESTTMP/t
562 pushing subrepo s/ss to $TESTTMP/t/s/ss
562 pushing subrepo s/ss to $TESTTMP/t/s/ss
563 searching for changes
563 searching for changes
564 no changes found
564 no changes found
565 pushing subrepo s to $TESTTMP/t/s
565 pushing subrepo s to $TESTTMP/t/s
566 searching for changes
566 searching for changes
567 adding changesets
567 adding changesets
568 adding manifests
568 adding manifests
569 adding file changes
569 adding file changes
570 added 1 changesets with 1 changes to 1 files (+1 heads)
570 added 1 changesets with 1 changes to 1 files (+1 heads)
571 pushing subrepo t to $TESTTMP/t/t
571 pushing subrepo t to $TESTTMP/t/t
572 searching for changes
572 searching for changes
573 no changes found
573 no changes found
574 searching for changes
574 searching for changes
575 adding changesets
575 adding changesets
576 adding manifests
576 adding manifests
577 adding file changes
577 adding file changes
578 added 1 changesets with 1 changes to 1 files
578 added 1 changesets with 1 changes to 1 files
579
579
580 check that unmodified subrepos are not pushed
580 check that unmodified subrepos are not pushed
581
581
582 $ hg clone . ../tcc
582 $ hg clone . ../tcc
583 updating to branch default
583 updating to branch default
584 cloning subrepo s from $TESTTMP/tc/s
584 cloning subrepo s from $TESTTMP/tc/s
585 cloning subrepo s/ss from $TESTTMP/tc/s/ss
585 cloning subrepo s/ss from $TESTTMP/tc/s/ss
586 cloning subrepo t from $TESTTMP/tc/t
586 cloning subrepo t from $TESTTMP/tc/t
587 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
587 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
588
588
589 the subrepos on the new clone have nothing to push to its source
589 the subrepos on the new clone have nothing to push to its source
590
590
591 $ hg push -R ../tcc .
591 $ hg push -R ../tcc .
592 pushing to .
592 pushing to .
593 no changes made to subrepo s/ss since last push to s/ss
593 no changes made to subrepo s/ss since last push to s/ss
594 no changes made to subrepo s since last push to s
594 no changes made to subrepo s since last push to s
595 no changes made to subrepo t since last push to t
595 no changes made to subrepo t since last push to t
596 searching for changes
596 searching for changes
597 no changes found
597 no changes found
598 [1]
598 [1]
599
599
600 the subrepos on the source do not have a clean store versus the clone target
600 the subrepos on the source do not have a clean store versus the clone target
601 because they were never explicitly pushed to the source
601 because they were never explicitly pushed to the source
602
602
603 $ hg push ../tcc
603 $ hg push ../tcc
604 pushing to ../tcc
604 pushing to ../tcc
605 pushing subrepo s/ss to ../tcc/s/ss
605 pushing subrepo s/ss to ../tcc/s/ss
606 searching for changes
606 searching for changes
607 no changes found
607 no changes found
608 pushing subrepo s to ../tcc/s
608 pushing subrepo s to ../tcc/s
609 searching for changes
609 searching for changes
610 no changes found
610 no changes found
611 pushing subrepo t to ../tcc/t
611 pushing subrepo t to ../tcc/t
612 searching for changes
612 searching for changes
613 no changes found
613 no changes found
614 searching for changes
614 searching for changes
615 no changes found
615 no changes found
616 [1]
616 [1]
617
617
618 after push their stores become clean
618 after push their stores become clean
619
619
620 $ hg push ../tcc
620 $ hg push ../tcc
621 pushing to ../tcc
621 pushing to ../tcc
622 no changes made to subrepo s/ss since last push to ../tcc/s/ss
622 no changes made to subrepo s/ss since last push to ../tcc/s/ss
623 no changes made to subrepo s since last push to ../tcc/s
623 no changes made to subrepo s since last push to ../tcc/s
624 no changes made to subrepo t since last push to ../tcc/t
624 no changes made to subrepo t since last push to ../tcc/t
625 searching for changes
625 searching for changes
626 no changes found
626 no changes found
627 [1]
627 [1]
628
628
629 updating a subrepo to a different revision or changing
629 updating a subrepo to a different revision or changing
630 its working directory does not make its store dirty
630 its working directory does not make its store dirty
631
631
632 $ hg -R s update '.^'
632 $ hg -R s update '.^'
633 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
633 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
634 $ hg push
634 $ hg push
635 pushing to $TESTTMP/t
635 pushing to $TESTTMP/t
636 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
636 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
637 no changes made to subrepo s since last push to $TESTTMP/t/s
637 no changes made to subrepo s since last push to $TESTTMP/t/s
638 no changes made to subrepo t since last push to $TESTTMP/t/t
638 no changes made to subrepo t since last push to $TESTTMP/t/t
639 searching for changes
639 searching for changes
640 no changes found
640 no changes found
641 [1]
641 [1]
642 $ echo foo >> s/a
642 $ echo foo >> s/a
643 $ hg push
643 $ hg push
644 pushing to $TESTTMP/t
644 pushing to $TESTTMP/t
645 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
645 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
646 no changes made to subrepo s since last push to $TESTTMP/t/s
646 no changes made to subrepo s since last push to $TESTTMP/t/s
647 no changes made to subrepo t since last push to $TESTTMP/t/t
647 no changes made to subrepo t since last push to $TESTTMP/t/t
648 searching for changes
648 searching for changes
649 no changes found
649 no changes found
650 [1]
650 [1]
651 $ hg -R s update -C tip
651 $ hg -R s update -C tip
652 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
652 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
653
653
654 committing into a subrepo makes its store (but not its parent's store) dirty
654 committing into a subrepo makes its store (but not its parent's store) dirty
655
655
656 $ echo foo >> s/ss/a
656 $ echo foo >> s/ss/a
657 $ hg -R s/ss commit -m 'test dirty store detection'
657 $ hg -R s/ss commit -m 'test dirty store detection'
658
658
659 $ hg out -S -r `hg log -r tip -T "{node|short}"`
659 $ hg out -S -r `hg log -r tip -T "{node|short}"`
660 comparing with $TESTTMP/t
660 comparing with $TESTTMP/t
661 searching for changes
661 searching for changes
662 no changes found
662 no changes found
663 comparing with $TESTTMP/t/s
663 comparing with $TESTTMP/t/s
664 searching for changes
664 searching for changes
665 no changes found
665 no changes found
666 comparing with $TESTTMP/t/s/ss
666 comparing with $TESTTMP/t/s/ss
667 searching for changes
667 searching for changes
668 changeset: 1:79ea5566a333
668 changeset: 1:79ea5566a333
669 tag: tip
669 tag: tip
670 user: test
670 user: test
671 date: Thu Jan 01 00:00:00 1970 +0000
671 date: Thu Jan 01 00:00:00 1970 +0000
672 summary: test dirty store detection
672 summary: test dirty store detection
673
673
674 comparing with $TESTTMP/t/t
674 comparing with $TESTTMP/t/t
675 searching for changes
675 searching for changes
676 no changes found
676 no changes found
677
677
678 $ hg push
678 $ hg push
679 pushing to $TESTTMP/t
679 pushing to $TESTTMP/t
680 pushing subrepo s/ss to $TESTTMP/t/s/ss
680 pushing subrepo s/ss to $TESTTMP/t/s/ss
681 searching for changes
681 searching for changes
682 adding changesets
682 adding changesets
683 adding manifests
683 adding manifests
684 adding file changes
684 adding file changes
685 added 1 changesets with 1 changes to 1 files
685 added 1 changesets with 1 changes to 1 files
686 no changes made to subrepo s since last push to $TESTTMP/t/s
686 no changes made to subrepo s since last push to $TESTTMP/t/s
687 no changes made to subrepo t since last push to $TESTTMP/t/t
687 no changes made to subrepo t since last push to $TESTTMP/t/t
688 searching for changes
688 searching for changes
689 no changes found
689 no changes found
690 [1]
690 [1]
691
691
692 a subrepo store may be clean versus one repo but not versus another
692 a subrepo store may be clean versus one repo but not versus another
693
693
694 $ hg push
694 $ hg push
695 pushing to $TESTTMP/t
695 pushing to $TESTTMP/t
696 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
696 no changes made to subrepo s/ss since last push to $TESTTMP/t/s/ss
697 no changes made to subrepo s since last push to $TESTTMP/t/s
697 no changes made to subrepo s since last push to $TESTTMP/t/s
698 no changes made to subrepo t since last push to $TESTTMP/t/t
698 no changes made to subrepo t since last push to $TESTTMP/t/t
699 searching for changes
699 searching for changes
700 no changes found
700 no changes found
701 [1]
701 [1]
702 $ hg push ../tcc
702 $ hg push ../tcc
703 pushing to ../tcc
703 pushing to ../tcc
704 pushing subrepo s/ss to ../tcc/s/ss
704 pushing subrepo s/ss to ../tcc/s/ss
705 searching for changes
705 searching for changes
706 adding changesets
706 adding changesets
707 adding manifests
707 adding manifests
708 adding file changes
708 adding file changes
709 added 1 changesets with 1 changes to 1 files
709 added 1 changesets with 1 changes to 1 files
710 no changes made to subrepo s since last push to ../tcc/s
710 no changes made to subrepo s since last push to ../tcc/s
711 no changes made to subrepo t since last push to ../tcc/t
711 no changes made to subrepo t since last push to ../tcc/t
712 searching for changes
712 searching for changes
713 no changes found
713 no changes found
714 [1]
714 [1]
715
715
716 update
716 update
717
717
718 $ cd ../t
718 $ cd ../t
719 $ hg up -C # discard our earlier merge
719 $ hg up -C # discard our earlier merge
720 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
720 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
721 updated to "c373c8102e68: 12"
721 updated to "c373c8102e68: 12"
722 2 other heads for branch "default"
722 2 other heads for branch "default"
723 $ echo blah > t/t
723 $ echo blah > t/t
724 $ hg ci -m13
724 $ hg ci -m13
725 committing subrepository t
725 committing subrepository t
726
726
727 backout calls revert internally with minimal opts, which should not raise
727 backout calls revert internally with minimal opts, which should not raise
728 KeyError
728 KeyError
729
729
730 $ hg backout ".^" --no-commit
730 $ hg backout ".^" --no-commit
731 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
731 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
732 changeset c373c8102e68 backed out, don't forget to commit.
732 changeset c373c8102e68 backed out, don't forget to commit.
733
733
734 $ hg up -C # discard changes
734 $ hg up -C # discard changes
735 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
735 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
736 updated to "925c17564ef8: 13"
736 updated to "925c17564ef8: 13"
737 2 other heads for branch "default"
737 2 other heads for branch "default"
738
738
739 pull
739 pull
740
740
741 $ cd ../tc
741 $ cd ../tc
742 $ hg pull
742 $ hg pull
743 pulling from $TESTTMP/t
743 pulling from $TESTTMP/t
744 searching for changes
744 searching for changes
745 adding changesets
745 adding changesets
746 adding manifests
746 adding manifests
747 adding file changes
747 adding file changes
748 added 1 changesets with 1 changes to 1 files
748 added 1 changesets with 1 changes to 1 files
749 new changesets 925c17564ef8
749 new changesets 925c17564ef8
750 (run 'hg update' to get a working copy)
750 (run 'hg update' to get a working copy)
751
751
752 should pull t
752 should pull t
753
753
754 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
754 $ hg incoming -S -r `hg log -r tip -T "{node|short}"`
755 comparing with $TESTTMP/t
755 comparing with $TESTTMP/t
756 no changes found
756 no changes found
757 comparing with $TESTTMP/t/s
757 comparing with $TESTTMP/t/s
758 searching for changes
758 searching for changes
759 no changes found
759 no changes found
760 comparing with $TESTTMP/t/s/ss
760 comparing with $TESTTMP/t/s/ss
761 searching for changes
761 searching for changes
762 no changes found
762 no changes found
763 comparing with $TESTTMP/t/t
763 comparing with $TESTTMP/t/t
764 searching for changes
764 searching for changes
765 changeset: 5:52c0adc0515a
765 changeset: 5:52c0adc0515a
766 tag: tip
766 tag: tip
767 user: test
767 user: test
768 date: Thu Jan 01 00:00:00 1970 +0000
768 date: Thu Jan 01 00:00:00 1970 +0000
769 summary: 13
769 summary: 13
770
770
771
771
772 $ hg up
772 $ hg up
773 pulling subrepo t from $TESTTMP/t/t
773 pulling subrepo t from $TESTTMP/t/t
774 searching for changes
774 searching for changes
775 adding changesets
775 adding changesets
776 adding manifests
776 adding manifests
777 adding file changes
777 adding file changes
778 added 1 changesets with 1 changes to 1 files
778 added 1 changesets with 1 changes to 1 files
779 new changesets 52c0adc0515a
779 new changesets 52c0adc0515a
780 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
780 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
781 updated to "925c17564ef8: 13"
781 updated to "925c17564ef8: 13"
782 2 other heads for branch "default"
782 2 other heads for branch "default"
783 $ cat t/t
783 $ cat t/t
784 blah
784 blah
785
785
786 bogus subrepo path aborts
786 bogus subrepo path aborts
787
787
788 $ echo 'bogus=[boguspath' >> .hgsub
788 $ echo 'bogus=[boguspath' >> .hgsub
789 $ hg ci -m 'bogus subrepo path'
789 $ hg ci -m 'bogus subrepo path'
790 abort: missing ] in subrepository source
790 abort: missing ] in subrepository source
791 [255]
791 [255]
792
792
793 Issue1986: merge aborts when trying to merge a subrepo that
793 Issue1986: merge aborts when trying to merge a subrepo that
794 shouldn't need merging
794 shouldn't need merging
795
795
796 # subrepo layout
796 # subrepo layout
797 #
797 #
798 # o 5 br
798 # o 5 br
799 # /|
799 # /|
800 # o | 4 default
800 # o | 4 default
801 # | |
801 # | |
802 # | o 3 br
802 # | o 3 br
803 # |/|
803 # |/|
804 # o | 2 default
804 # o | 2 default
805 # | |
805 # | |
806 # | o 1 br
806 # | o 1 br
807 # |/
807 # |/
808 # o 0 default
808 # o 0 default
809
809
810 $ cd ..
810 $ cd ..
811 $ rm -rf sub
811 $ rm -rf sub
812 $ hg init main
812 $ hg init main
813 $ cd main
813 $ cd main
814 $ hg init s
814 $ hg init s
815 $ cd s
815 $ cd s
816 $ echo a > a
816 $ echo a > a
817 $ hg ci -Am1
817 $ hg ci -Am1
818 adding a
818 adding a
819 $ hg branch br
819 $ hg branch br
820 marked working directory as branch br
820 marked working directory as branch br
821 (branches are permanent and global, did you want a bookmark?)
821 (branches are permanent and global, did you want a bookmark?)
822 $ echo a >> a
822 $ echo a >> a
823 $ hg ci -m1
823 $ hg ci -m1
824 $ hg up default
824 $ hg up default
825 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
825 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
826 $ echo b > b
826 $ echo b > b
827 $ hg ci -Am1
827 $ hg ci -Am1
828 adding b
828 adding b
829 $ hg up br
829 $ hg up br
830 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
830 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
831 $ hg merge tip
831 $ hg merge tip
832 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
832 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
833 (branch merge, don't forget to commit)
833 (branch merge, don't forget to commit)
834 $ hg ci -m1
834 $ hg ci -m1
835 $ hg up 2
835 $ hg up 2
836 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
836 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
837 $ echo c > c
837 $ echo c > c
838 $ hg ci -Am1
838 $ hg ci -Am1
839 adding c
839 adding c
840 $ hg up 3
840 $ hg up 3
841 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
841 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
842 $ hg merge 4
842 $ hg merge 4
843 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
843 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
844 (branch merge, don't forget to commit)
844 (branch merge, don't forget to commit)
845 $ hg ci -m1
845 $ hg ci -m1
846
846
847 # main repo layout:
847 # main repo layout:
848 #
848 #
849 # * <-- try to merge default into br again
849 # * <-- try to merge default into br again
850 # .`|
850 # .`|
851 # . o 5 br --> substate = 5
851 # . o 5 br --> substate = 5
852 # . |
852 # . |
853 # o | 4 default --> substate = 4
853 # o | 4 default --> substate = 4
854 # | |
854 # | |
855 # | o 3 br --> substate = 2
855 # | o 3 br --> substate = 2
856 # |/|
856 # |/|
857 # o | 2 default --> substate = 2
857 # o | 2 default --> substate = 2
858 # | |
858 # | |
859 # | o 1 br --> substate = 3
859 # | o 1 br --> substate = 3
860 # |/
860 # |/
861 # o 0 default --> substate = 2
861 # o 0 default --> substate = 2
862
862
863 $ cd ..
863 $ cd ..
864 $ echo 's = s' > .hgsub
864 $ echo 's = s' > .hgsub
865 $ hg -R s up 2
865 $ hg -R s up 2
866 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
866 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
867 $ hg ci -Am1
867 $ hg ci -Am1
868 adding .hgsub
868 adding .hgsub
869 $ hg branch br
869 $ hg branch br
870 marked working directory as branch br
870 marked working directory as branch br
871 (branches are permanent and global, did you want a bookmark?)
871 (branches are permanent and global, did you want a bookmark?)
872 $ echo b > b
872 $ echo b > b
873 $ hg -R s up 3
873 $ hg -R s up 3
874 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
874 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
875 $ hg ci -Am1
875 $ hg ci -Am1
876 adding b
876 adding b
877 $ hg up default
877 $ hg up default
878 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
878 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
879 $ echo c > c
879 $ echo c > c
880 $ hg ci -Am1
880 $ hg ci -Am1
881 adding c
881 adding c
882 $ hg up 1
882 $ hg up 1
883 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
883 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
884 $ hg merge 2
884 $ hg merge 2
885 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
885 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
886 (branch merge, don't forget to commit)
886 (branch merge, don't forget to commit)
887 $ hg ci -m1
887 $ hg ci -m1
888 $ hg up 2
888 $ hg up 2
889 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
889 1 files updated, 0 files merged, 1 files removed, 0 files unresolved
890 $ hg -R s up 4
890 $ hg -R s up 4
891 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
891 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
892 $ echo d > d
892 $ echo d > d
893 $ hg ci -Am1
893 $ hg ci -Am1
894 adding d
894 adding d
895 $ hg up 3
895 $ hg up 3
896 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
896 2 files updated, 0 files merged, 1 files removed, 0 files unresolved
897 $ hg -R s up 5
897 $ hg -R s up 5
898 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
898 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
899 $ echo e > e
899 $ echo e > e
900 $ hg ci -Am1
900 $ hg ci -Am1
901 adding e
901 adding e
902
902
903 $ hg up 5
903 $ hg up 5
904 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
904 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
905 $ hg merge 4 # try to merge default into br again
905 $ hg merge 4 # try to merge default into br again
906 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
906 subrepository s diverged (local revision: f8f13b33206e, remote revision: a3f9062a4f88)
907 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
907 (M)erge, keep (l)ocal [working copy] or keep (r)emote [merge rev]? m
908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
908 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
909 (branch merge, don't forget to commit)
909 (branch merge, don't forget to commit)
910 $ cd ..
910 $ cd ..
911
911
912 test subrepo delete from .hgsubstate
912 test subrepo delete from .hgsubstate
913
913
914 $ hg init testdelete
914 $ hg init testdelete
915 $ mkdir testdelete/nested testdelete/nested2
915 $ mkdir testdelete/nested testdelete/nested2
916 $ hg init testdelete/nested
916 $ hg init testdelete/nested
917 $ hg init testdelete/nested2
917 $ hg init testdelete/nested2
918 $ echo test > testdelete/nested/foo
918 $ echo test > testdelete/nested/foo
919 $ echo test > testdelete/nested2/foo
919 $ echo test > testdelete/nested2/foo
920 $ hg -R testdelete/nested add
920 $ hg -R testdelete/nested add
921 adding testdelete/nested/foo
921 adding testdelete/nested/foo
922 $ hg -R testdelete/nested2 add
922 $ hg -R testdelete/nested2 add
923 adding testdelete/nested2/foo
923 adding testdelete/nested2/foo
924 $ hg -R testdelete/nested ci -m test
924 $ hg -R testdelete/nested ci -m test
925 $ hg -R testdelete/nested2 ci -m test
925 $ hg -R testdelete/nested2 ci -m test
926 $ echo nested = nested > testdelete/.hgsub
926 $ echo nested = nested > testdelete/.hgsub
927 $ echo nested2 = nested2 >> testdelete/.hgsub
927 $ echo nested2 = nested2 >> testdelete/.hgsub
928 $ hg -R testdelete add
928 $ hg -R testdelete add
929 adding testdelete/.hgsub
929 adding testdelete/.hgsub
930 $ hg -R testdelete ci -m "nested 1 & 2 added"
930 $ hg -R testdelete ci -m "nested 1 & 2 added"
931 $ echo nested = nested > testdelete/.hgsub
931 $ echo nested = nested > testdelete/.hgsub
932 $ hg -R testdelete ci -m "nested 2 deleted"
932 $ hg -R testdelete ci -m "nested 2 deleted"
933 $ cat testdelete/.hgsubstate
933 $ cat testdelete/.hgsubstate
934 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
934 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
935 $ hg -R testdelete remove testdelete/.hgsub
935 $ hg -R testdelete remove testdelete/.hgsub
936 $ hg -R testdelete ci -m ".hgsub deleted"
936 $ hg -R testdelete ci -m ".hgsub deleted"
937 $ cat testdelete/.hgsubstate
937 $ cat testdelete/.hgsubstate
938 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
938 bdf5c9a3103743d900b12ae0db3ffdcfd7b0d878 nested
939
939
940 test repository cloning
940 test repository cloning
941
941
942 $ mkdir mercurial mercurial2
942 $ mkdir mercurial mercurial2
943 $ hg init nested_absolute
943 $ hg init nested_absolute
944 $ echo test > nested_absolute/foo
944 $ echo test > nested_absolute/foo
945 $ hg -R nested_absolute add
945 $ hg -R nested_absolute add
946 adding nested_absolute/foo
946 adding nested_absolute/foo
947 $ hg -R nested_absolute ci -mtest
947 $ hg -R nested_absolute ci -mtest
948 $ cd mercurial
948 $ cd mercurial
949 $ hg init nested_relative
949 $ hg init nested_relative
950 $ echo test2 > nested_relative/foo2
950 $ echo test2 > nested_relative/foo2
951 $ hg -R nested_relative add
951 $ hg -R nested_relative add
952 adding nested_relative/foo2
952 adding nested_relative/foo2
953 $ hg -R nested_relative ci -mtest2
953 $ hg -R nested_relative ci -mtest2
954 $ hg init main
954 $ hg init main
955 $ echo "nested_relative = ../nested_relative" > main/.hgsub
955 $ echo "nested_relative = ../nested_relative" > main/.hgsub
956 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
956 $ echo "nested_absolute = `pwd`/nested_absolute" >> main/.hgsub
957 $ hg -R main add
957 $ hg -R main add
958 adding main/.hgsub
958 adding main/.hgsub
959 $ hg -R main ci -m "add subrepos"
959 $ hg -R main ci -m "add subrepos"
960 $ cd ..
960 $ cd ..
961 $ hg clone mercurial/main mercurial2/main
961 $ hg clone mercurial/main mercurial2/main
962 updating to branch default
962 updating to branch default
963 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
963 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
964 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
964 $ cat mercurial2/main/nested_absolute/.hg/hgrc \
965 > mercurial2/main/nested_relative/.hg/hgrc
965 > mercurial2/main/nested_relative/.hg/hgrc
966 [paths]
966 [paths]
967 default = $TESTTMP/mercurial/nested_absolute
967 default = $TESTTMP/mercurial/nested_absolute
968 [paths]
968 [paths]
969 default = $TESTTMP/mercurial/nested_relative
969 default = $TESTTMP/mercurial/nested_relative
970 $ rm -rf mercurial mercurial2
970 $ rm -rf mercurial mercurial2
971
971
972 Issue1977: multirepo push should fail if subrepo push fails
972 Issue1977: multirepo push should fail if subrepo push fails
973
973
974 $ hg init repo
974 $ hg init repo
975 $ hg init repo/s
975 $ hg init repo/s
976 $ echo a > repo/s/a
976 $ echo a > repo/s/a
977 $ hg -R repo/s ci -Am0
977 $ hg -R repo/s ci -Am0
978 adding a
978 adding a
979 $ echo s = s > repo/.hgsub
979 $ echo s = s > repo/.hgsub
980 $ hg -R repo ci -Am1
980 $ hg -R repo ci -Am1
981 adding .hgsub
981 adding .hgsub
982 $ hg clone repo repo2
982 $ hg clone repo repo2
983 updating to branch default
983 updating to branch default
984 cloning subrepo s from $TESTTMP/repo/s
984 cloning subrepo s from $TESTTMP/repo/s
985 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
985 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
986 $ hg -q -R repo2 pull -u
986 $ hg -q -R repo2 pull -u
987 $ echo 1 > repo2/s/a
987 $ echo 1 > repo2/s/a
988 $ hg -R repo2/s ci -m2
988 $ hg -R repo2/s ci -m2
989 $ hg -q -R repo2/s push
989 $ hg -q -R repo2/s push
990 $ hg -R repo2/s up -C 0
990 $ hg -R repo2/s up -C 0
991 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
991 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
992 $ echo 2 > repo2/s/b
992 $ echo 2 > repo2/s/b
993 $ hg -R repo2/s ci -m3 -A
993 $ hg -R repo2/s ci -m3 -A
994 adding b
994 adding b
995 created new head
995 created new head
996 $ hg -R repo2 ci -m3
996 $ hg -R repo2 ci -m3
997 $ hg -q -R repo2 push
997 $ hg -q -R repo2 push
998 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
998 abort: push creates new remote head cc505f09a8b2! (in subrepository "s")
999 (merge or see 'hg help push' for details about pushing new heads)
999 (merge or see 'hg help push' for details about pushing new heads)
1000 [255]
1000 [255]
1001 $ hg -R repo update
1001 $ hg -R repo update
1002 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1002 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1003
1003
1004 test if untracked file is not overwritten
1004 test if untracked file is not overwritten
1005
1005
1006 (this also tests that updated .hgsubstate is treated as "modified",
1006 (this also tests that updated .hgsubstate is treated as "modified",
1007 when 'merge.update()' is aborted before 'merge.recordupdates()', even
1007 when 'merge.update()' is aborted before 'merge.recordupdates()', even
1008 if none of mode, size and timestamp of it isn't changed on the
1008 if none of mode, size and timestamp of it isn't changed on the
1009 filesystem (see also issue4583))
1009 filesystem (see also issue4583))
1010
1010
1011 $ echo issue3276_ok > repo/s/b
1011 $ echo issue3276_ok > repo/s/b
1012 $ hg -R repo2 push -f -q
1012 $ hg -R repo2 push -f -q
1013 $ touch -t 200001010000 repo/.hgsubstate
1013 $ touch -t 200001010000 repo/.hgsubstate
1014
1014
1015 $ cat >> repo/.hg/hgrc <<EOF
1015 $ cat >> repo/.hg/hgrc <<EOF
1016 > [fakedirstatewritetime]
1016 > [fakedirstatewritetime]
1017 > # emulate invoking dirstate.write() via repo.status()
1017 > # emulate invoking dirstate.write() via repo.status()
1018 > # at 2000-01-01 00:00
1018 > # at 2000-01-01 00:00
1019 > fakenow = 200001010000
1019 > fakenow = 200001010000
1020 >
1020 >
1021 > [extensions]
1021 > [extensions]
1022 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1022 > fakedirstatewritetime = $TESTDIR/fakedirstatewritetime.py
1023 > EOF
1023 > EOF
1024 $ hg -R repo update
1024 $ hg -R repo update
1025 b: untracked file differs
1025 b: untracked file differs
1026 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
1026 abort: untracked files in working directory differ from files in requested revision (in subrepository "s")
1027 [255]
1027 [255]
1028 $ cat >> repo/.hg/hgrc <<EOF
1028 $ cat >> repo/.hg/hgrc <<EOF
1029 > [extensions]
1029 > [extensions]
1030 > fakedirstatewritetime = !
1030 > fakedirstatewritetime = !
1031 > EOF
1031 > EOF
1032
1032
1033 $ cat repo/s/b
1033 $ cat repo/s/b
1034 issue3276_ok
1034 issue3276_ok
1035 $ rm repo/s/b
1035 $ rm repo/s/b
1036 $ touch -t 200001010000 repo/.hgsubstate
1036 $ touch -t 200001010000 repo/.hgsubstate
1037 $ hg -R repo revert --all
1037 $ hg -R repo revert --all
1038 reverting repo/.hgsubstate
1038 reverting repo/.hgsubstate
1039 reverting subrepo s
1039 reverting subrepo s
1040 $ hg -R repo update
1040 $ hg -R repo update
1041 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1041 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1042 $ cat repo/s/b
1042 $ cat repo/s/b
1043 2
1043 2
1044 $ rm -rf repo2 repo
1044 $ rm -rf repo2 repo
1045
1045
1046
1046
1047 Issue1852 subrepos with relative paths always push/pull relative to default
1047 Issue1852 subrepos with relative paths always push/pull relative to default
1048
1048
1049 Prepare a repo with subrepo
1049 Prepare a repo with subrepo
1050
1050
1051 $ hg init issue1852a
1051 $ hg init issue1852a
1052 $ cd issue1852a
1052 $ cd issue1852a
1053 $ hg init sub/repo
1053 $ hg init sub/repo
1054 $ echo test > sub/repo/foo
1054 $ echo test > sub/repo/foo
1055 $ hg -R sub/repo add sub/repo/foo
1055 $ hg -R sub/repo add sub/repo/foo
1056 $ echo sub/repo = sub/repo > .hgsub
1056 $ echo sub/repo = sub/repo > .hgsub
1057 $ hg add .hgsub
1057 $ hg add .hgsub
1058 $ hg ci -mtest
1058 $ hg ci -mtest
1059 committing subrepository sub/repo
1059 committing subrepository sub/repo
1060 $ echo test >> sub/repo/foo
1060 $ echo test >> sub/repo/foo
1061 $ hg ci -mtest
1061 $ hg ci -mtest
1062 committing subrepository sub/repo
1062 committing subrepository sub/repo
1063 $ hg cat sub/repo/foo
1063 $ hg cat sub/repo/foo
1064 test
1064 test
1065 test
1065 test
1066 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1066 $ hg cat sub/repo/foo -Tjson | sed 's|\\\\|/|g'
1067 [
1067 [
1068 {
1068 {
1069 "abspath": "foo",
1069 "abspath": "foo",
1070 "data": "test\ntest\n",
1070 "data": "test\ntest\n",
1071 "path": "sub/repo/foo"
1071 "path": "sub/repo/foo"
1072 }
1072 }
1073 ]
1073 ]
1074
1074
1075 non-exact match:
1075 non-exact match:
1076
1076
1077 $ hg cat -T '{path}\n' 'glob:**'
1077 $ hg cat -T '{path}\n' 'glob:**'
1078 .hgsub
1078 .hgsub
1079 .hgsubstate
1079 .hgsubstate
1080 sub/repo/foo
1080 sub/repo/foo
1081 $ hg cat -T '{path}\n' 're:^sub'
1081 $ hg cat -T '{path}\n' 're:^sub'
1082 sub/repo/foo
1082 sub/repo/foo
1083
1083
1084 missing subrepos in working directory:
1084 missing subrepos in working directory:
1085
1085
1086 $ mkdir -p tmp/sub/repo
1086 $ mkdir -p tmp/sub/repo
1087 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1087 $ hg cat -r 0 --output tmp/%p_p sub/repo/foo
1088 $ cat tmp/sub/repo/foo_p
1088 $ cat tmp/sub/repo/foo_p
1089 test
1089 test
1090 $ mv sub/repo sub_
1090 $ mv sub/repo sub_
1091 $ hg cat sub/repo/baz
1091 $ hg cat sub/repo/baz
1092 skipping missing subrepository: sub/repo
1092 skipping missing subrepository: sub/repo
1093 [1]
1093 [1]
1094 $ rm -rf sub/repo
1094 $ rm -rf sub/repo
1095 $ mv sub_ sub/repo
1095 $ mv sub_ sub/repo
1096 $ cd ..
1096 $ cd ..
1097
1097
1098 Create repo without default path, pull top repo, and see what happens on update
1098 Create repo without default path, pull top repo, and see what happens on update
1099
1099
1100 $ hg init issue1852b
1100 $ hg init issue1852b
1101 $ hg -R issue1852b pull issue1852a
1101 $ hg -R issue1852b pull issue1852a
1102 pulling from issue1852a
1102 pulling from issue1852a
1103 requesting all changes
1103 requesting all changes
1104 adding changesets
1104 adding changesets
1105 adding manifests
1105 adding manifests
1106 adding file changes
1106 adding file changes
1107 added 2 changesets with 3 changes to 2 files
1107 added 2 changesets with 3 changes to 2 files
1108 new changesets 19487b456929:be5eb94e7215
1108 new changesets 19487b456929:be5eb94e7215
1109 (run 'hg update' to get a working copy)
1109 (run 'hg update' to get a working copy)
1110 $ hg -R issue1852b update
1110 $ hg -R issue1852b update
1111 abort: default path for subrepository not found (in subrepository "sub/repo")
1111 abort: default path for subrepository not found (in subrepository "sub/repo")
1112 [255]
1112 [255]
1113
1113
1114 Ensure a full traceback, not just the SubrepoAbort part
1114 Ensure a full traceback, not just the SubrepoAbort part
1115
1115
1116 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1116 $ hg -R issue1852b update --traceback 2>&1 | grep 'raise error\.Abort'
1117 raise error.Abort(_("default path for subrepository not found"))
1117 raise error.Abort(_("default path for subrepository not found"))
1118
1118
1119 Pull -u now doesn't help
1119 Pull -u now doesn't help
1120
1120
1121 $ hg -R issue1852b pull -u issue1852a
1121 $ hg -R issue1852b pull -u issue1852a
1122 pulling from issue1852a
1122 pulling from issue1852a
1123 searching for changes
1123 searching for changes
1124 no changes found
1124 no changes found
1125
1125
1126 Try the same, but with pull -u
1126 Try the same, but with pull -u
1127
1127
1128 $ hg init issue1852c
1128 $ hg init issue1852c
1129 $ hg -R issue1852c pull -r0 -u issue1852a
1129 $ hg -R issue1852c pull -r0 -u issue1852a
1130 pulling from issue1852a
1130 pulling from issue1852a
1131 adding changesets
1131 adding changesets
1132 adding manifests
1132 adding manifests
1133 adding file changes
1133 adding file changes
1134 added 1 changesets with 2 changes to 2 files
1134 added 1 changesets with 2 changes to 2 files
1135 new changesets 19487b456929
1135 new changesets 19487b456929
1136 cloning subrepo sub/repo from issue1852a/sub/repo
1136 cloning subrepo sub/repo from issue1852a/sub/repo
1137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1137 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1138
1138
1139 Try to push from the other side
1139 Try to push from the other side
1140
1140
1141 $ hg -R issue1852a push `pwd`/issue1852c
1141 $ hg -R issue1852a push `pwd`/issue1852c
1142 pushing to $TESTTMP/issue1852c
1142 pushing to $TESTTMP/issue1852c
1143 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo
1143 pushing subrepo sub/repo to $TESTTMP/issue1852c/sub/repo
1144 searching for changes
1144 searching for changes
1145 no changes found
1145 no changes found
1146 searching for changes
1146 searching for changes
1147 adding changesets
1147 adding changesets
1148 adding manifests
1148 adding manifests
1149 adding file changes
1149 adding file changes
1150 added 1 changesets with 1 changes to 1 files
1150 added 1 changesets with 1 changes to 1 files
1151
1151
1152 Incoming and outgoing should not use the default path:
1152 Incoming and outgoing should not use the default path:
1153
1153
1154 $ hg clone -q issue1852a issue1852d
1154 $ hg clone -q issue1852a issue1852d
1155 $ hg -R issue1852d outgoing --subrepos issue1852c
1155 $ hg -R issue1852d outgoing --subrepos issue1852c
1156 comparing with issue1852c
1156 comparing with issue1852c
1157 searching for changes
1157 searching for changes
1158 no changes found
1158 no changes found
1159 comparing with issue1852c/sub/repo
1159 comparing with issue1852c/sub/repo
1160 searching for changes
1160 searching for changes
1161 no changes found
1161 no changes found
1162 [1]
1162 [1]
1163 $ hg -R issue1852d incoming --subrepos issue1852c
1163 $ hg -R issue1852d incoming --subrepos issue1852c
1164 comparing with issue1852c
1164 comparing with issue1852c
1165 searching for changes
1165 searching for changes
1166 no changes found
1166 no changes found
1167 comparing with issue1852c/sub/repo
1167 comparing with issue1852c/sub/repo
1168 searching for changes
1168 searching for changes
1169 no changes found
1169 no changes found
1170 [1]
1170 [1]
1171
1171
1172 Check that merge of a new subrepo doesn't write the uncommitted state to
1172 Check that merge of a new subrepo doesn't write the uncommitted state to
1173 .hgsubstate (issue4622)
1173 .hgsubstate (issue4622)
1174
1174
1175 $ hg init issue1852a/addedsub
1175 $ hg init issue1852a/addedsub
1176 $ echo zzz > issue1852a/addedsub/zz.txt
1176 $ echo zzz > issue1852a/addedsub/zz.txt
1177 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1177 $ hg -R issue1852a/addedsub ci -Aqm "initial ZZ"
1178
1178
1179 $ hg clone issue1852a/addedsub issue1852d/addedsub
1179 $ hg clone issue1852a/addedsub issue1852d/addedsub
1180 updating to branch default
1180 updating to branch default
1181 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1181 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1182
1182
1183 $ echo def > issue1852a/sub/repo/foo
1183 $ echo def > issue1852a/sub/repo/foo
1184 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1184 $ hg -R issue1852a ci -SAm 'tweaked subrepo'
1185 adding tmp/sub/repo/foo_p
1185 adding tmp/sub/repo/foo_p
1186 committing subrepository sub/repo
1186 committing subrepository sub/repo
1187
1187
1188 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1188 $ echo 'addedsub = addedsub' >> issue1852d/.hgsub
1189 $ echo xyz > issue1852d/sub/repo/foo
1189 $ echo xyz > issue1852d/sub/repo/foo
1190 $ hg -R issue1852d pull -u
1190 $ hg -R issue1852d pull -u
1191 pulling from $TESTTMP/issue1852a
1191 pulling from $TESTTMP/issue1852a
1192 searching for changes
1192 searching for changes
1193 adding changesets
1193 adding changesets
1194 adding manifests
1194 adding manifests
1195 adding file changes
1195 adding file changes
1196 added 1 changesets with 2 changes to 2 files
1196 added 1 changesets with 2 changes to 2 files
1197 new changesets c82b79fdcc5b
1197 new changesets c82b79fdcc5b
1198 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1198 subrepository sub/repo diverged (local revision: f42d5c7504a8, remote revision: 46cd4aac504c)
1199 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1199 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1200 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo
1200 pulling subrepo sub/repo from $TESTTMP/issue1852a/sub/repo
1201 searching for changes
1201 searching for changes
1202 adding changesets
1202 adding changesets
1203 adding manifests
1203 adding manifests
1204 adding file changes
1204 adding file changes
1205 added 1 changesets with 1 changes to 1 files
1205 added 1 changesets with 1 changes to 1 files
1206 new changesets 46cd4aac504c
1206 new changesets 46cd4aac504c
1207 subrepository sources for sub/repo differ
1207 subrepository sources for sub/repo differ
1208 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1208 use (l)ocal source (f42d5c7504a8) or (r)emote source (46cd4aac504c)? l
1209 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1209 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1210 $ cat issue1852d/.hgsubstate
1210 $ cat issue1852d/.hgsubstate
1211 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1211 f42d5c7504a811dda50f5cf3e5e16c3330b87172 sub/repo
1212
1212
1213 Check status of files when none of them belong to the first
1213 Check status of files when none of them belong to the first
1214 subrepository:
1214 subrepository:
1215
1215
1216 $ hg init subrepo-status
1216 $ hg init subrepo-status
1217 $ cd subrepo-status
1217 $ cd subrepo-status
1218 $ hg init subrepo-1
1218 $ hg init subrepo-1
1219 $ hg init subrepo-2
1219 $ hg init subrepo-2
1220 $ cd subrepo-2
1220 $ cd subrepo-2
1221 $ touch file
1221 $ touch file
1222 $ hg add file
1222 $ hg add file
1223 $ cd ..
1223 $ cd ..
1224 $ echo subrepo-1 = subrepo-1 > .hgsub
1224 $ echo subrepo-1 = subrepo-1 > .hgsub
1225 $ echo subrepo-2 = subrepo-2 >> .hgsub
1225 $ echo subrepo-2 = subrepo-2 >> .hgsub
1226 $ hg add .hgsub
1226 $ hg add .hgsub
1227 $ hg ci -m 'Added subrepos'
1227 $ hg ci -m 'Added subrepos'
1228 committing subrepository subrepo-2
1228 committing subrepository subrepo-2
1229 $ hg st subrepo-2/file
1229 $ hg st subrepo-2/file
1230
1230
1231 Check that share works with subrepo
1231 Check that share works with subrepo
1232 $ hg --config extensions.share= share . ../shared
1232 $ hg --config extensions.share= share . ../shared
1233 updating working directory
1233 updating working directory
1234 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1234 sharing subrepo subrepo-1 from $TESTTMP/subrepo-status/subrepo-1
1235 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1235 sharing subrepo subrepo-2 from $TESTTMP/subrepo-status/subrepo-2
1236 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1236 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1237 $ find ../shared/* | sort
1237 $ find ../shared/* | sort
1238 ../shared/subrepo-1
1238 ../shared/subrepo-1
1239 ../shared/subrepo-1/.hg
1239 ../shared/subrepo-1/.hg
1240 ../shared/subrepo-1/.hg/cache
1240 ../shared/subrepo-1/.hg/cache
1241 ../shared/subrepo-1/.hg/cache/storehash
1241 ../shared/subrepo-1/.hg/cache/storehash
1242 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1242 ../shared/subrepo-1/.hg/cache/storehash/* (glob)
1243 ../shared/subrepo-1/.hg/hgrc
1243 ../shared/subrepo-1/.hg/hgrc
1244 ../shared/subrepo-1/.hg/requires
1244 ../shared/subrepo-1/.hg/requires
1245 ../shared/subrepo-1/.hg/sharedpath
1245 ../shared/subrepo-1/.hg/sharedpath
1246 ../shared/subrepo-2
1246 ../shared/subrepo-2
1247 ../shared/subrepo-2/.hg
1247 ../shared/subrepo-2/.hg
1248 ../shared/subrepo-2/.hg/branch
1248 ../shared/subrepo-2/.hg/branch
1249 ../shared/subrepo-2/.hg/cache
1249 ../shared/subrepo-2/.hg/cache
1250 ../shared/subrepo-2/.hg/cache/checkisexec (execbit !)
1250 ../shared/subrepo-2/.hg/cache/checkisexec (execbit !)
1251 ../shared/subrepo-2/.hg/cache/checklink (symlink !)
1251 ../shared/subrepo-2/.hg/cache/checklink (symlink !)
1252 ../shared/subrepo-2/.hg/cache/checklink-target (symlink !)
1252 ../shared/subrepo-2/.hg/cache/checklink-target (symlink !)
1253 ../shared/subrepo-2/.hg/cache/storehash
1253 ../shared/subrepo-2/.hg/cache/storehash
1254 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1254 ../shared/subrepo-2/.hg/cache/storehash/* (glob)
1255 ../shared/subrepo-2/.hg/dirstate
1255 ../shared/subrepo-2/.hg/dirstate
1256 ../shared/subrepo-2/.hg/hgrc
1256 ../shared/subrepo-2/.hg/hgrc
1257 ../shared/subrepo-2/.hg/requires
1257 ../shared/subrepo-2/.hg/requires
1258 ../shared/subrepo-2/.hg/sharedpath
1258 ../shared/subrepo-2/.hg/sharedpath
1259 ../shared/subrepo-2/file
1259 ../shared/subrepo-2/file
1260 $ hg -R ../shared in
1260 $ hg -R ../shared in
1261 abort: repository default not found!
1261 abort: repository default not found!
1262 [255]
1262 [255]
1263 $ hg -R ../shared/subrepo-2 showconfig paths
1263 $ hg -R ../shared/subrepo-2 showconfig paths
1264 paths.default=$TESTTMP/subrepo-status/subrepo-2
1264 paths.default=$TESTTMP/subrepo-status/subrepo-2
1265 $ hg -R ../shared/subrepo-1 sum --remote
1265 $ hg -R ../shared/subrepo-1 sum --remote
1266 parent: -1:000000000000 tip (empty repository)
1266 parent: -1:000000000000 tip (empty repository)
1267 branch: default
1267 branch: default
1268 commit: (clean)
1268 commit: (clean)
1269 update: (current)
1269 update: (current)
1270 remote: (synced)
1270 remote: (synced)
1271
1271
1272 Check hg update --clean
1272 Check hg update --clean
1273 $ cd $TESTTMP/t
1273 $ cd $TESTTMP/t
1274 $ rm -r t/t.orig
1274 $ rm -r t/t.orig
1275 $ hg status -S --all
1275 $ hg status -S --all
1276 C .hgsub
1276 C .hgsub
1277 C .hgsubstate
1277 C .hgsubstate
1278 C a
1278 C a
1279 C s/.hgsub
1279 C s/.hgsub
1280 C s/.hgsubstate
1280 C s/.hgsubstate
1281 C s/a
1281 C s/a
1282 C s/ss/a
1282 C s/ss/a
1283 C t/t
1283 C t/t
1284 $ echo c1 > s/a
1284 $ echo c1 > s/a
1285 $ cd s
1285 $ cd s
1286 $ echo c1 > b
1286 $ echo c1 > b
1287 $ echo c1 > c
1287 $ echo c1 > c
1288 $ hg add b
1288 $ hg add b
1289 $ cd ..
1289 $ cd ..
1290 $ hg status -S
1290 $ hg status -S
1291 M s/a
1291 M s/a
1292 A s/b
1292 A s/b
1293 ? s/c
1293 ? s/c
1294 $ hg update -C
1294 $ hg update -C
1295 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1295 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1296 updated to "925c17564ef8: 13"
1296 updated to "925c17564ef8: 13"
1297 2 other heads for branch "default"
1297 2 other heads for branch "default"
1298 $ hg status -S
1298 $ hg status -S
1299 ? s/b
1299 ? s/b
1300 ? s/c
1300 ? s/c
1301
1301
1302 Sticky subrepositories, no changes
1302 Sticky subrepositories, no changes
1303 $ cd $TESTTMP/t
1303 $ cd $TESTTMP/t
1304 $ hg id
1304 $ hg id
1305 925c17564ef8 tip
1305 925c17564ef8 tip
1306 $ hg -R s id
1306 $ hg -R s id
1307 12a213df6fa9 tip
1307 12a213df6fa9 tip
1308 $ hg -R t id
1308 $ hg -R t id
1309 52c0adc0515a tip
1309 52c0adc0515a tip
1310 $ hg update 11
1310 $ hg update 11
1311 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1311 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1312 $ hg id
1312 $ hg id
1313 365661e5936a
1313 365661e5936a
1314 $ hg -R s id
1314 $ hg -R s id
1315 fc627a69481f
1315 fc627a69481f
1316 $ hg -R t id
1316 $ hg -R t id
1317 e95bcfa18a35
1317 e95bcfa18a35
1318
1318
1319 Sticky subrepositories, file changes
1319 Sticky subrepositories, file changes
1320 $ touch s/f1
1320 $ touch s/f1
1321 $ touch t/f1
1321 $ touch t/f1
1322 $ hg add -S s/f1
1322 $ hg add -S s/f1
1323 $ hg add -S t/f1
1323 $ hg add -S t/f1
1324 $ hg id
1324 $ hg id
1325 365661e5936a+
1325 365661e5936a+
1326 $ hg -R s id
1326 $ hg -R s id
1327 fc627a69481f+
1327 fc627a69481f+
1328 $ hg -R t id
1328 $ hg -R t id
1329 e95bcfa18a35+
1329 e95bcfa18a35+
1330 $ hg update tip
1330 $ hg update tip
1331 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1331 subrepository s diverged (local revision: fc627a69481f, remote revision: 12a213df6fa9)
1332 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1332 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1333 subrepository sources for s differ
1333 subrepository sources for s differ
1334 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1334 use (l)ocal source (fc627a69481f) or (r)emote source (12a213df6fa9)? l
1335 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1335 subrepository t diverged (local revision: e95bcfa18a35, remote revision: 52c0adc0515a)
1336 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1336 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1337 subrepository sources for t differ
1337 subrepository sources for t differ
1338 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1338 use (l)ocal source (e95bcfa18a35) or (r)emote source (52c0adc0515a)? l
1339 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1339 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1340 $ hg id
1340 $ hg id
1341 925c17564ef8+ tip
1341 925c17564ef8+ tip
1342 $ hg -R s id
1342 $ hg -R s id
1343 fc627a69481f+
1343 fc627a69481f+
1344 $ hg -R t id
1344 $ hg -R t id
1345 e95bcfa18a35+
1345 e95bcfa18a35+
1346 $ hg update --clean tip
1346 $ hg update --clean tip
1347 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1347 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1348
1348
1349 Sticky subrepository, revision updates
1349 Sticky subrepository, revision updates
1350 $ hg id
1350 $ hg id
1351 925c17564ef8 tip
1351 925c17564ef8 tip
1352 $ hg -R s id
1352 $ hg -R s id
1353 12a213df6fa9 tip
1353 12a213df6fa9 tip
1354 $ hg -R t id
1354 $ hg -R t id
1355 52c0adc0515a tip
1355 52c0adc0515a tip
1356 $ cd s
1356 $ cd s
1357 $ hg update -r -2
1357 $ hg update -r -2
1358 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1358 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1359 $ cd ../t
1359 $ cd ../t
1360 $ hg update -r 2
1360 $ hg update -r 2
1361 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1361 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1362 $ cd ..
1362 $ cd ..
1363 $ hg update 10
1363 $ hg update 10
1364 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1364 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1365 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1365 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1366 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1366 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 20a0db6fbf6c)
1367 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1367 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1368 subrepository sources for t differ (in checked out version)
1368 subrepository sources for t differ (in checked out version)
1369 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1369 use (l)ocal source (7af322bc1198) or (r)emote source (20a0db6fbf6c)? l
1370 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1370 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1371 $ hg id
1371 $ hg id
1372 e45c8b14af55+
1372 e45c8b14af55+
1373 $ hg -R s id
1373 $ hg -R s id
1374 02dcf1d70411
1374 02dcf1d70411
1375 $ hg -R t id
1375 $ hg -R t id
1376 7af322bc1198
1376 7af322bc1198
1377
1377
1378 Sticky subrepository, file changes and revision updates
1378 Sticky subrepository, file changes and revision updates
1379 $ touch s/f1
1379 $ touch s/f1
1380 $ touch t/f1
1380 $ touch t/f1
1381 $ hg add -S s/f1
1381 $ hg add -S s/f1
1382 $ hg add -S t/f1
1382 $ hg add -S t/f1
1383 $ hg id
1383 $ hg id
1384 e45c8b14af55+
1384 e45c8b14af55+
1385 $ hg -R s id
1385 $ hg -R s id
1386 02dcf1d70411+
1386 02dcf1d70411+
1387 $ hg -R t id
1387 $ hg -R t id
1388 7af322bc1198+
1388 7af322bc1198+
1389 $ hg update tip
1389 $ hg update tip
1390 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1390 subrepository s diverged (local revision: 12a213df6fa9, remote revision: 12a213df6fa9)
1391 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1391 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1392 subrepository sources for s differ
1392 subrepository sources for s differ
1393 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1393 use (l)ocal source (02dcf1d70411) or (r)emote source (12a213df6fa9)? l
1394 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1394 subrepository t diverged (local revision: 52c0adc0515a, remote revision: 52c0adc0515a)
1395 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1395 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1396 subrepository sources for t differ
1396 subrepository sources for t differ
1397 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1397 use (l)ocal source (7af322bc1198) or (r)emote source (52c0adc0515a)? l
1398 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1398 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1399 $ hg id
1399 $ hg id
1400 925c17564ef8+ tip
1400 925c17564ef8+ tip
1401 $ hg -R s id
1401 $ hg -R s id
1402 02dcf1d70411+
1402 02dcf1d70411+
1403 $ hg -R t id
1403 $ hg -R t id
1404 7af322bc1198+
1404 7af322bc1198+
1405
1405
1406 Sticky repository, update --clean
1406 Sticky repository, update --clean
1407 $ hg update --clean tip
1407 $ hg update --clean tip
1408 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1408 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1409 $ hg id
1409 $ hg id
1410 925c17564ef8 tip
1410 925c17564ef8 tip
1411 $ hg -R s id
1411 $ hg -R s id
1412 12a213df6fa9 tip
1412 12a213df6fa9 tip
1413 $ hg -R t id
1413 $ hg -R t id
1414 52c0adc0515a tip
1414 52c0adc0515a tip
1415
1415
1416 Test subrepo already at intended revision:
1416 Test subrepo already at intended revision:
1417 $ cd s
1417 $ cd s
1418 $ hg update fc627a69481f
1418 $ hg update fc627a69481f
1419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1419 1 files updated, 0 files merged, 0 files removed, 0 files unresolved
1420 $ cd ..
1420 $ cd ..
1421 $ hg update 11
1421 $ hg update 11
1422 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1422 subrepository s diverged (local revision: 12a213df6fa9, remote revision: fc627a69481f)
1423 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1423 (M)erge, keep (l)ocal [working copy] or keep (r)emote [destination]? m
1424 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1424 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1425 0 files updated, 0 files merged, 0 files removed, 0 files unresolved
1426 $ hg id -n
1426 $ hg id -n
1427 11+
1427 11+
1428 $ hg -R s id
1428 $ hg -R s id
1429 fc627a69481f
1429 fc627a69481f
1430 $ hg -R t id
1430 $ hg -R t id
1431 e95bcfa18a35
1431 e95bcfa18a35
1432
1432
1433 Test that removing .hgsubstate doesn't break anything:
1433 Test that removing .hgsubstate doesn't break anything:
1434
1434
1435 $ hg rm -f .hgsubstate
1435 $ hg rm -f .hgsubstate
1436 $ hg ci -mrm
1436 $ hg ci -mrm
1437 nothing changed
1437 nothing changed
1438 [1]
1438 [1]
1439 $ hg log -vr tip
1439 $ hg log -vr tip
1440 changeset: 13:925c17564ef8
1440 changeset: 13:925c17564ef8
1441 tag: tip
1441 tag: tip
1442 user: test
1442 user: test
1443 date: Thu Jan 01 00:00:00 1970 +0000
1443 date: Thu Jan 01 00:00:00 1970 +0000
1444 files: .hgsubstate
1444 files: .hgsubstate
1445 description:
1445 description:
1446 13
1446 13
1447
1447
1448
1448
1449
1449
1450 Test that removing .hgsub removes .hgsubstate:
1450 Test that removing .hgsub removes .hgsubstate:
1451
1451
1452 $ hg rm .hgsub
1452 $ hg rm .hgsub
1453 $ hg ci -mrm2
1453 $ hg ci -mrm2
1454 created new head
1454 created new head
1455 $ hg log -vr tip
1455 $ hg log -vr tip
1456 changeset: 14:2400bccd50af
1456 changeset: 14:2400bccd50af
1457 tag: tip
1457 tag: tip
1458 parent: 11:365661e5936a
1458 parent: 11:365661e5936a
1459 user: test
1459 user: test
1460 date: Thu Jan 01 00:00:00 1970 +0000
1460 date: Thu Jan 01 00:00:00 1970 +0000
1461 files: .hgsub .hgsubstate
1461 files: .hgsub .hgsubstate
1462 description:
1462 description:
1463 rm2
1463 rm2
1464
1464
1465
1465
1466 Test issue3153: diff -S with deleted subrepos
1466 Test issue3153: diff -S with deleted subrepos
1467
1467
1468 $ hg diff --nodates -S -c .
1468 $ hg diff --nodates -S -c .
1469 diff -r 365661e5936a -r 2400bccd50af .hgsub
1469 diff -r 365661e5936a -r 2400bccd50af .hgsub
1470 --- a/.hgsub
1470 --- a/.hgsub
1471 +++ /dev/null
1471 +++ /dev/null
1472 @@ -1,2 +0,0 @@
1472 @@ -1,2 +0,0 @@
1473 -s = s
1473 -s = s
1474 -t = t
1474 -t = t
1475 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1475 diff -r 365661e5936a -r 2400bccd50af .hgsubstate
1476 --- a/.hgsubstate
1476 --- a/.hgsubstate
1477 +++ /dev/null
1477 +++ /dev/null
1478 @@ -1,2 +0,0 @@
1478 @@ -1,2 +0,0 @@
1479 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1479 -fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1480 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1480 -e95bcfa18a358dc4936da981ebf4147b4cad1362 t
1481
1481
1482 Test behavior of add for explicit path in subrepo:
1482 Test behavior of add for explicit path in subrepo:
1483 $ cd ..
1483 $ cd ..
1484 $ hg init explicit
1484 $ hg init explicit
1485 $ cd explicit
1485 $ cd explicit
1486 $ echo s = s > .hgsub
1486 $ echo s = s > .hgsub
1487 $ hg add .hgsub
1487 $ hg add .hgsub
1488 $ hg init s
1488 $ hg init s
1489 $ hg ci -m0
1489 $ hg ci -m0
1490 Adding with an explicit path in a subrepo adds the file
1490 Adding with an explicit path in a subrepo adds the file
1491 $ echo c1 > f1
1491 $ echo c1 > f1
1492 $ echo c2 > s/f2
1492 $ echo c2 > s/f2
1493 $ hg st -S
1493 $ hg st -S
1494 ? f1
1494 ? f1
1495 ? s/f2
1495 ? s/f2
1496 $ hg add s/f2
1496 $ hg add s/f2
1497 $ hg st -S
1497 $ hg st -S
1498 A s/f2
1498 A s/f2
1499 ? f1
1499 ? f1
1500 $ hg ci -R s -m0
1500 $ hg ci -R s -m0
1501 $ hg ci -Am1
1501 $ hg ci -Am1
1502 adding f1
1502 adding f1
1503 Adding with an explicit path in a subrepo with -S has the same behavior
1503 Adding with an explicit path in a subrepo with -S has the same behavior
1504 $ echo c3 > f3
1504 $ echo c3 > f3
1505 $ echo c4 > s/f4
1505 $ echo c4 > s/f4
1506 $ hg st -S
1506 $ hg st -S
1507 ? f3
1507 ? f3
1508 ? s/f4
1508 ? s/f4
1509 $ hg add -S s/f4
1509 $ hg add -S s/f4
1510 $ hg st -S
1510 $ hg st -S
1511 A s/f4
1511 A s/f4
1512 ? f3
1512 ? f3
1513 $ hg ci -R s -m1
1513 $ hg ci -R s -m1
1514 $ hg ci -Ama2
1514 $ hg ci -Ama2
1515 adding f3
1515 adding f3
1516 Adding without a path or pattern silently ignores subrepos
1516 Adding without a path or pattern silently ignores subrepos
1517 $ echo c5 > f5
1517 $ echo c5 > f5
1518 $ echo c6 > s/f6
1518 $ echo c6 > s/f6
1519 $ echo c7 > s/f7
1519 $ echo c7 > s/f7
1520 $ hg st -S
1520 $ hg st -S
1521 ? f5
1521 ? f5
1522 ? s/f6
1522 ? s/f6
1523 ? s/f7
1523 ? s/f7
1524 $ hg add
1524 $ hg add
1525 adding f5
1525 adding f5
1526 $ hg st -S
1526 $ hg st -S
1527 A f5
1527 A f5
1528 ? s/f6
1528 ? s/f6
1529 ? s/f7
1529 ? s/f7
1530 $ hg ci -R s -Am2
1530 $ hg ci -R s -Am2
1531 adding f6
1531 adding f6
1532 adding f7
1532 adding f7
1533 $ hg ci -m3
1533 $ hg ci -m3
1534 Adding without a path or pattern with -S also adds files in subrepos
1534 Adding without a path or pattern with -S also adds files in subrepos
1535 $ echo c8 > f8
1535 $ echo c8 > f8
1536 $ echo c9 > s/f9
1536 $ echo c9 > s/f9
1537 $ echo c10 > s/f10
1537 $ echo c10 > s/f10
1538 $ hg st -S
1538 $ hg st -S
1539 ? f8
1539 ? f8
1540 ? s/f10
1540 ? s/f10
1541 ? s/f9
1541 ? s/f9
1542 $ hg add -S
1542 $ hg add -S
1543 adding f8
1543 adding f8
1544 adding s/f10
1544 adding s/f10
1545 adding s/f9
1545 adding s/f9
1546 $ hg st -S
1546 $ hg st -S
1547 A f8
1547 A f8
1548 A s/f10
1548 A s/f10
1549 A s/f9
1549 A s/f9
1550 $ hg ci -R s -m3
1550 $ hg ci -R s -m3
1551 $ hg ci -m4
1551 $ hg ci -m4
1552 Adding with a pattern silently ignores subrepos
1552 Adding with a pattern silently ignores subrepos
1553 $ echo c11 > fm11
1553 $ echo c11 > fm11
1554 $ echo c12 > fn12
1554 $ echo c12 > fn12
1555 $ echo c13 > s/fm13
1555 $ echo c13 > s/fm13
1556 $ echo c14 > s/fn14
1556 $ echo c14 > s/fn14
1557 $ hg st -S
1557 $ hg st -S
1558 ? fm11
1558 ? fm11
1559 ? fn12
1559 ? fn12
1560 ? s/fm13
1560 ? s/fm13
1561 ? s/fn14
1561 ? s/fn14
1562 $ hg add 'glob:**fm*'
1562 $ hg add 'glob:**fm*'
1563 adding fm11
1563 adding fm11
1564 $ hg st -S
1564 $ hg st -S
1565 A fm11
1565 A fm11
1566 ? fn12
1566 ? fn12
1567 ? s/fm13
1567 ? s/fm13
1568 ? s/fn14
1568 ? s/fn14
1569 $ hg ci -R s -Am4
1569 $ hg ci -R s -Am4
1570 adding fm13
1570 adding fm13
1571 adding fn14
1571 adding fn14
1572 $ hg ci -Am5
1572 $ hg ci -Am5
1573 adding fn12
1573 adding fn12
1574 Adding with a pattern with -S also adds matches in subrepos
1574 Adding with a pattern with -S also adds matches in subrepos
1575 $ echo c15 > fm15
1575 $ echo c15 > fm15
1576 $ echo c16 > fn16
1576 $ echo c16 > fn16
1577 $ echo c17 > s/fm17
1577 $ echo c17 > s/fm17
1578 $ echo c18 > s/fn18
1578 $ echo c18 > s/fn18
1579 $ hg st -S
1579 $ hg st -S
1580 ? fm15
1580 ? fm15
1581 ? fn16
1581 ? fn16
1582 ? s/fm17
1582 ? s/fm17
1583 ? s/fn18
1583 ? s/fn18
1584 $ hg add -S 'glob:**fm*'
1584 $ hg add -S 'glob:**fm*'
1585 adding fm15
1585 adding fm15
1586 adding s/fm17
1586 adding s/fm17
1587 $ hg st -S
1587 $ hg st -S
1588 A fm15
1588 A fm15
1589 A s/fm17
1589 A s/fm17
1590 ? fn16
1590 ? fn16
1591 ? s/fn18
1591 ? s/fn18
1592 $ hg ci -R s -Am5
1592 $ hg ci -R s -Am5
1593 adding fn18
1593 adding fn18
1594 $ hg ci -Am6
1594 $ hg ci -Am6
1595 adding fn16
1595 adding fn16
1596
1596
1597 Test behavior of forget for explicit path in subrepo:
1597 Test behavior of forget for explicit path in subrepo:
1598 Forgetting an explicit path in a subrepo untracks the file
1598 Forgetting an explicit path in a subrepo untracks the file
1599 $ echo c19 > s/f19
1599 $ echo c19 > s/f19
1600 $ hg add s/f19
1600 $ hg add s/f19
1601 $ hg st -S
1601 $ hg st -S
1602 A s/f19
1602 A s/f19
1603 $ hg forget s/f19
1603 $ hg forget s/f19
1604 $ hg st -S
1604 $ hg st -S
1605 ? s/f19
1605 ? s/f19
1606 $ rm s/f19
1606 $ rm s/f19
1607 $ cd ..
1607 $ cd ..
1608
1608
1609 Courtesy phases synchronisation to publishing server does not block the push
1609 Courtesy phases synchronisation to publishing server does not block the push
1610 (issue3781)
1610 (issue3781)
1611
1611
1612 $ cp -R main issue3781
1612 $ cp -R main issue3781
1613 $ cp -R main issue3781-dest
1613 $ cp -R main issue3781-dest
1614 $ cd issue3781-dest/s
1614 $ cd issue3781-dest/s
1615 $ hg phase tip # show we have draft changeset
1615 $ hg phase tip # show we have draft changeset
1616 5: draft
1616 5: draft
1617 $ chmod a-w .hg/store/phaseroots # prevent phase push
1617 $ chmod a-w .hg/store/phaseroots # prevent phase push
1618 $ cd ../../issue3781
1618 $ cd ../../issue3781
1619 $ cat >> .hg/hgrc << EOF
1619 $ cat >> .hg/hgrc << EOF
1620 > [paths]
1620 > [paths]
1621 > default=../issue3781-dest/
1621 > default=../issue3781-dest/
1622 > EOF
1622 > EOF
1623 $ hg push --config devel.legacy.exchange=bundle1
1623 $ hg push --config devel.legacy.exchange=bundle1
1624 pushing to $TESTTMP/issue3781-dest
1624 pushing to $TESTTMP/issue3781-dest
1625 pushing subrepo s to $TESTTMP/issue3781-dest/s
1625 pushing subrepo s to $TESTTMP/issue3781-dest/s
1626 searching for changes
1626 searching for changes
1627 no changes found
1627 no changes found
1628 searching for changes
1628 searching for changes
1629 no changes found
1629 no changes found
1630 [1]
1630 [1]
1631 # clean the push cache
1631 # clean the push cache
1632 $ rm s/.hg/cache/storehash/*
1632 $ rm s/.hg/cache/storehash/*
1633 $ hg push # bundle2+
1633 $ hg push # bundle2+
1634 pushing to $TESTTMP/issue3781-dest
1634 pushing to $TESTTMP/issue3781-dest
1635 pushing subrepo s to $TESTTMP/issue3781-dest/s
1635 pushing subrepo s to $TESTTMP/issue3781-dest/s
1636 searching for changes
1636 searching for changes
1637 no changes found
1637 no changes found
1638 searching for changes
1638 searching for changes
1639 no changes found
1639 no changes found
1640 [1]
1640 [1]
1641 $ cd ..
1641 $ cd ..
1642
1642
1643 Test phase choice for newly created commit with "phases.subrepochecks"
1643 Test phase choice for newly created commit with "phases.subrepochecks"
1644 configuration
1644 configuration
1645
1645
1646 $ cd t
1646 $ cd t
1647 $ hg update -q -r 12
1647 $ hg update -q -r 12
1648
1648
1649 $ cat >> s/ss/.hg/hgrc <<EOF
1649 $ cat >> s/ss/.hg/hgrc <<EOF
1650 > [phases]
1650 > [phases]
1651 > new-commit = secret
1651 > new-commit = secret
1652 > EOF
1652 > EOF
1653 $ cat >> s/.hg/hgrc <<EOF
1653 $ cat >> s/.hg/hgrc <<EOF
1654 > [phases]
1654 > [phases]
1655 > new-commit = draft
1655 > new-commit = draft
1656 > EOF
1656 > EOF
1657 $ echo phasecheck1 >> s/ss/a
1657 $ echo phasecheck1 >> s/ss/a
1658 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1658 $ hg -R s commit -S --config phases.checksubrepos=abort -m phasecheck1
1659 committing subrepository ss
1659 committing subrepository ss
1660 transaction abort!
1660 transaction abort!
1661 rollback completed
1661 rollback completed
1662 abort: can't commit in draft phase conflicting secret from subrepository ss
1662 abort: can't commit in draft phase conflicting secret from subrepository ss
1663 [255]
1663 [255]
1664 $ echo phasecheck2 >> s/ss/a
1664 $ echo phasecheck2 >> s/ss/a
1665 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1665 $ hg -R s commit -S --config phases.checksubrepos=ignore -m phasecheck2
1666 committing subrepository ss
1666 committing subrepository ss
1667 $ hg -R s/ss phase tip
1667 $ hg -R s/ss phase tip
1668 3: secret
1668 3: secret
1669 $ hg -R s phase tip
1669 $ hg -R s phase tip
1670 6: draft
1670 6: draft
1671 $ echo phasecheck3 >> s/ss/a
1671 $ echo phasecheck3 >> s/ss/a
1672 $ hg -R s commit -S -m phasecheck3
1672 $ hg -R s commit -S -m phasecheck3
1673 committing subrepository ss
1673 committing subrepository ss
1674 warning: changes are committed in secret phase from subrepository ss
1674 warning: changes are committed in secret phase from subrepository ss
1675 $ hg -R s/ss phase tip
1675 $ hg -R s/ss phase tip
1676 4: secret
1676 4: secret
1677 $ hg -R s phase tip
1677 $ hg -R s phase tip
1678 7: secret
1678 7: secret
1679
1679
1680 $ cat >> t/.hg/hgrc <<EOF
1680 $ cat >> t/.hg/hgrc <<EOF
1681 > [phases]
1681 > [phases]
1682 > new-commit = draft
1682 > new-commit = draft
1683 > EOF
1683 > EOF
1684 $ cat >> .hg/hgrc <<EOF
1684 $ cat >> .hg/hgrc <<EOF
1685 > [phases]
1685 > [phases]
1686 > new-commit = public
1686 > new-commit = public
1687 > EOF
1687 > EOF
1688 $ echo phasecheck4 >> s/ss/a
1688 $ echo phasecheck4 >> s/ss/a
1689 $ echo phasecheck4 >> t/t
1689 $ echo phasecheck4 >> t/t
1690 $ hg commit -S -m phasecheck4
1690 $ hg commit -S -m phasecheck4
1691 committing subrepository s
1691 committing subrepository s
1692 committing subrepository s/ss
1692 committing subrepository s/ss
1693 warning: changes are committed in secret phase from subrepository ss
1693 warning: changes are committed in secret phase from subrepository ss
1694 committing subrepository t
1694 committing subrepository t
1695 warning: changes are committed in secret phase from subrepository s
1695 warning: changes are committed in secret phase from subrepository s
1696 created new head
1696 created new head
1697 $ hg -R s/ss phase tip
1697 $ hg -R s/ss phase tip
1698 5: secret
1698 5: secret
1699 $ hg -R s phase tip
1699 $ hg -R s phase tip
1700 8: secret
1700 8: secret
1701 $ hg -R t phase tip
1701 $ hg -R t phase tip
1702 6: draft
1702 6: draft
1703 $ hg phase tip
1703 $ hg phase tip
1704 15: secret
1704 15: secret
1705
1705
1706 $ cd ..
1706 $ cd ..
1707
1707
1708
1708
1709 Test that commit --secret works on both repo and subrepo (issue4182)
1709 Test that commit --secret works on both repo and subrepo (issue4182)
1710
1710
1711 $ cd main
1711 $ cd main
1712 $ echo secret >> b
1712 $ echo secret >> b
1713 $ echo secret >> s/b
1713 $ echo secret >> s/b
1714 $ hg commit --secret --subrepo -m "secret"
1714 $ hg commit --secret --subrepo -m "secret"
1715 committing subrepository s
1715 committing subrepository s
1716 $ hg phase -r .
1716 $ hg phase -r .
1717 6: secret
1717 6: secret
1718 $ cd s
1718 $ cd s
1719 $ hg phase -r .
1719 $ hg phase -r .
1720 6: secret
1720 6: secret
1721 $ cd ../../
1721 $ cd ../../
1722
1722
1723 Test "subrepos" template keyword
1723 Test "subrepos" template keyword
1724
1724
1725 $ cd t
1725 $ cd t
1726 $ hg update -q 15
1726 $ hg update -q 15
1727 $ cat > .hgsub <<EOF
1727 $ cat > .hgsub <<EOF
1728 > s = s
1728 > s = s
1729 > EOF
1729 > EOF
1730 $ hg commit -m "16"
1730 $ hg commit -m "16"
1731 warning: changes are committed in secret phase from subrepository s
1731 warning: changes are committed in secret phase from subrepository s
1732
1732
1733 (addition of ".hgsub" itself)
1733 (addition of ".hgsub" itself)
1734
1734
1735 $ hg diff --nodates -c 1 .hgsubstate
1735 $ hg diff --nodates -c 1 .hgsubstate
1736 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1736 diff -r f7b1eb17ad24 -r 7cf8cfea66e4 .hgsubstate
1737 --- /dev/null
1737 --- /dev/null
1738 +++ b/.hgsubstate
1738 +++ b/.hgsubstate
1739 @@ -0,0 +1,1 @@
1739 @@ -0,0 +1,1 @@
1740 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1740 +e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1741 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1741 $ hg log -r 1 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1742 f7b1eb17ad24 000000000000
1742 f7b1eb17ad24 000000000000
1743 s
1743 s
1744
1744
1745 (modification of existing entry)
1745 (modification of existing entry)
1746
1746
1747 $ hg diff --nodates -c 2 .hgsubstate
1747 $ hg diff --nodates -c 2 .hgsubstate
1748 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1748 diff -r 7cf8cfea66e4 -r df30734270ae .hgsubstate
1749 --- a/.hgsubstate
1749 --- a/.hgsubstate
1750 +++ b/.hgsubstate
1750 +++ b/.hgsubstate
1751 @@ -1,1 +1,1 @@
1751 @@ -1,1 +1,1 @@
1752 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1752 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1753 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1753 +dc73e2e6d2675eb2e41e33c205f4bdab4ea5111d s
1754 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1754 $ hg log -r 2 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1755 7cf8cfea66e4 000000000000
1755 7cf8cfea66e4 000000000000
1756 s
1756 s
1757
1757
1758 (addition of entry)
1758 (addition of entry)
1759
1759
1760 $ hg diff --nodates -c 5 .hgsubstate
1760 $ hg diff --nodates -c 5 .hgsubstate
1761 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1761 diff -r 7cf8cfea66e4 -r 1f14a2e2d3ec .hgsubstate
1762 --- a/.hgsubstate
1762 --- a/.hgsubstate
1763 +++ b/.hgsubstate
1763 +++ b/.hgsubstate
1764 @@ -1,1 +1,2 @@
1764 @@ -1,1 +1,2 @@
1765 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1765 e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1766 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1766 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1767 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1767 $ hg log -r 5 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1768 7cf8cfea66e4 000000000000
1768 7cf8cfea66e4 000000000000
1769 t
1769 t
1770
1770
1771 (removal of existing entry)
1771 (removal of existing entry)
1772
1772
1773 $ hg diff --nodates -c 16 .hgsubstate
1773 $ hg diff --nodates -c 16 .hgsubstate
1774 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1774 diff -r 8bec38d2bd0b -r f2f70bc3d3c9 .hgsubstate
1775 --- a/.hgsubstate
1775 --- a/.hgsubstate
1776 +++ b/.hgsubstate
1776 +++ b/.hgsubstate
1777 @@ -1,2 +1,1 @@
1777 @@ -1,2 +1,1 @@
1778 0731af8ca9423976d3743119d0865097c07bdc1b s
1778 0731af8ca9423976d3743119d0865097c07bdc1b s
1779 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1779 -e202dc79b04c88a636ea8913d9182a1346d9b3dc t
1780 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1780 $ hg log -r 16 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1781 8bec38d2bd0b 000000000000
1781 8bec38d2bd0b 000000000000
1782 t
1782 t
1783
1783
1784 (merging)
1784 (merging)
1785
1785
1786 $ hg diff --nodates -c 9 .hgsubstate
1786 $ hg diff --nodates -c 9 .hgsubstate
1787 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1787 diff -r f6affe3fbfaa -r f0d2028bf86d .hgsubstate
1788 --- a/.hgsubstate
1788 --- a/.hgsubstate
1789 +++ b/.hgsubstate
1789 +++ b/.hgsubstate
1790 @@ -1,1 +1,2 @@
1790 @@ -1,1 +1,2 @@
1791 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1791 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1792 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1792 +60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
1793 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1793 $ hg log -r 9 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1794 f6affe3fbfaa 1f14a2e2d3ec
1794 f6affe3fbfaa 1f14a2e2d3ec
1795 t
1795 t
1796
1796
1797 (removal of ".hgsub" itself)
1797 (removal of ".hgsub" itself)
1798
1798
1799 $ hg diff --nodates -c 8 .hgsubstate
1799 $ hg diff --nodates -c 8 .hgsubstate
1800 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1800 diff -r f94576341bcf -r 96615c1dad2d .hgsubstate
1801 --- a/.hgsubstate
1801 --- a/.hgsubstate
1802 +++ /dev/null
1802 +++ /dev/null
1803 @@ -1,2 +0,0 @@
1803 @@ -1,2 +0,0 @@
1804 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1804 -e4ece1bf43360ddc8f6a96432201a37b7cd27ae4 s
1805 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1805 -7af322bc1198a32402fe903e0b7ebcfc5c9bf8f4 t
1806 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1806 $ hg log -r 8 --template "{p1node|short} {p2node|short}\n{subrepos % '{subrepo}\n'}"
1807 f94576341bcf 000000000000
1807 f94576341bcf 000000000000
1808
1808
1809 Test that '[paths]' is configured correctly at subrepo creation
1809 Test that '[paths]' is configured correctly at subrepo creation
1810
1810
1811 $ cd $TESTTMP/tc
1811 $ cd $TESTTMP/tc
1812 $ cat > .hgsub <<EOF
1812 $ cat > .hgsub <<EOF
1813 > # to clear bogus subrepo path 'bogus=[boguspath'
1813 > # to clear bogus subrepo path 'bogus=[boguspath'
1814 > s = s
1814 > s = s
1815 > t = t
1815 > t = t
1816 > EOF
1816 > EOF
1817 $ hg update -q --clean null
1817 $ hg update -q --clean null
1818 $ rm -rf s t
1818 $ rm -rf s t
1819 $ cat >> .hg/hgrc <<EOF
1819 $ cat >> .hg/hgrc <<EOF
1820 > [paths]
1820 > [paths]
1821 > default-push = /foo/bar
1821 > default-push = /foo/bar
1822 > EOF
1822 > EOF
1823 $ hg update -q
1823 $ hg update -q
1824 $ cat s/.hg/hgrc
1824 $ cat s/.hg/hgrc
1825 [paths]
1825 [paths]
1826 default = $TESTTMP/t/s
1826 default = $TESTTMP/t/s
1827 default-push = /foo/bar/s
1827 default-push = /foo/bar/s
1828 $ cat s/ss/.hg/hgrc
1828 $ cat s/ss/.hg/hgrc
1829 [paths]
1829 [paths]
1830 default = $TESTTMP/t/s/ss
1830 default = $TESTTMP/t/s/ss
1831 default-push = /foo/bar/s/ss
1831 default-push = /foo/bar/s/ss
1832 $ cat t/.hg/hgrc
1832 $ cat t/.hg/hgrc
1833 [paths]
1833 [paths]
1834 default = $TESTTMP/t/t
1834 default = $TESTTMP/t/t
1835 default-push = /foo/bar/t
1835 default-push = /foo/bar/t
1836
1836
1837 $ cd $TESTTMP/t
1837 $ cd $TESTTMP/t
1838 $ hg up -qC 0
1838 $ hg up -qC 0
1839 $ echo 'bar' > bar.txt
1839 $ echo 'bar' > bar.txt
1840 $ hg ci -Am 'branch before subrepo add'
1840 $ hg ci -Am 'branch before subrepo add'
1841 adding bar.txt
1841 adding bar.txt
1842 created new head
1842 created new head
1843 $ hg merge -r "first(subrepo('s'))"
1843 $ hg merge -r "first(subrepo('s'))"
1844 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1844 2 files updated, 0 files merged, 0 files removed, 0 files unresolved
1845 (branch merge, don't forget to commit)
1845 (branch merge, don't forget to commit)
1846 $ hg status -S -X '.hgsub*'
1846 $ hg status -S -X '.hgsub*'
1847 A s/a
1847 A s/a
1848 ? s/b
1848 ? s/b
1849 ? s/c
1849 ? s/c
1850 ? s/f1
1850 ? s/f1
1851 $ hg status -S --rev 'p2()'
1851 $ hg status -S --rev 'p2()'
1852 A bar.txt
1852 A bar.txt
1853 ? s/b
1853 ? s/b
1854 ? s/c
1854 ? s/c
1855 ? s/f1
1855 ? s/f1
1856 $ hg diff -S -X '.hgsub*' --nodates
1856 $ hg diff -S -X '.hgsub*' --nodates
1857 diff -r 000000000000 s/a
1857 diff -r 000000000000 s/a
1858 --- /dev/null
1858 --- /dev/null
1859 +++ b/s/a
1859 +++ b/s/a
1860 @@ -0,0 +1,1 @@
1860 @@ -0,0 +1,1 @@
1861 +a
1861 +a
1862 $ hg diff -S --rev 'p2()' --nodates
1862 $ hg diff -S --rev 'p2()' --nodates
1863 diff -r 7cf8cfea66e4 bar.txt
1863 diff -r 7cf8cfea66e4 bar.txt
1864 --- /dev/null
1864 --- /dev/null
1865 +++ b/bar.txt
1865 +++ b/bar.txt
1866 @@ -0,0 +1,1 @@
1866 @@ -0,0 +1,1 @@
1867 +bar
1867 +bar
1868
1868
1869 $ cd ..
1869 $ cd ..
1870
1870
1871 test for ssh exploit 2017-07-25
1871 test for ssh exploit 2017-07-25
1872
1872
1873 $ cat >> $HGRCPATH << EOF
1873 $ cat >> $HGRCPATH << EOF
1874 > [ui]
1874 > [ui]
1875 > ssh = sh -c "read l; read l; read l"
1875 > ssh = sh -c "read l; read l; read l"
1876 > EOF
1876 > EOF
1877
1877
1878 $ hg init malicious-proxycommand
1878 $ hg init malicious-proxycommand
1879 $ cd malicious-proxycommand
1879 $ cd malicious-proxycommand
1880 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1880 $ echo 's = [hg]ssh://-oProxyCommand=touch${IFS}owned/path' > .hgsub
1881 $ hg init s
1881 $ hg init s
1882 $ cd s
1882 $ cd s
1883 $ echo init > init
1883 $ echo init > init
1884 $ hg add
1884 $ hg add
1885 adding init
1885 adding init
1886 $ hg commit -m init
1886 $ hg commit -m init
1887 $ cd ..
1887 $ cd ..
1888 $ hg add .hgsub
1888 $ hg add .hgsub
1889 $ hg ci -m 'add subrepo'
1889 $ hg ci -m 'add subrepo'
1890 $ cd ..
1890 $ cd ..
1891 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1891 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1892 updating to branch default
1892 updating to branch default
1893 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1893 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1894 [255]
1894 [255]
1895
1895
1896 also check that a percent encoded '-' (%2D) doesn't work
1896 also check that a percent encoded '-' (%2D) doesn't work
1897
1897
1898 $ cd malicious-proxycommand
1898 $ cd malicious-proxycommand
1899 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1899 $ echo 's = [hg]ssh://%2DoProxyCommand=touch${IFS}owned/path' > .hgsub
1900 $ hg ci -m 'change url to percent encoded'
1900 $ hg ci -m 'change url to percent encoded'
1901 $ cd ..
1901 $ cd ..
1902 $ rm -r malicious-proxycommand-clone
1902 $ rm -r malicious-proxycommand-clone
1903 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1903 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1904 updating to branch default
1904 updating to branch default
1905 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1905 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch${IFS}owned/path' (in subrepository "s")
1906 [255]
1906 [255]
1907
1907
1908 also check for a pipe
1908 also check for a pipe
1909
1909
1910 $ cd malicious-proxycommand
1910 $ cd malicious-proxycommand
1911 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1911 $ echo 's = [hg]ssh://fakehost|touch${IFS}owned/path' > .hgsub
1912 $ hg ci -m 'change url to pipe'
1912 $ hg ci -m 'change url to pipe'
1913 $ cd ..
1913 $ cd ..
1914 $ rm -r malicious-proxycommand-clone
1914 $ rm -r malicious-proxycommand-clone
1915 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1915 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1916 updating to branch default
1916 updating to branch default
1917 abort: no suitable response from remote hg!
1917 abort: no suitable response from remote hg!
1918 [255]
1918 [255]
1919 $ [ ! -f owned ] || echo 'you got owned'
1919 $ [ ! -f owned ] || echo 'you got owned'
1920
1920
1921 also check that a percent encoded '|' (%7C) doesn't work
1921 also check that a percent encoded '|' (%7C) doesn't work
1922
1922
1923 $ cd malicious-proxycommand
1923 $ cd malicious-proxycommand
1924 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1924 $ echo 's = [hg]ssh://fakehost%7Ctouch%20owned/path' > .hgsub
1925 $ hg ci -m 'change url to percent encoded pipe'
1925 $ hg ci -m 'change url to percent encoded pipe'
1926 $ cd ..
1926 $ cd ..
1927 $ rm -r malicious-proxycommand-clone
1927 $ rm -r malicious-proxycommand-clone
1928 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1928 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1929 updating to branch default
1929 updating to branch default
1930 abort: no suitable response from remote hg!
1930 abort: no suitable response from remote hg!
1931 [255]
1931 [255]
1932 $ [ ! -f owned ] || echo 'you got owned'
1932 $ [ ! -f owned ] || echo 'you got owned'
1933
1933
1934 and bad usernames:
1934 and bad usernames:
1935 $ cd malicious-proxycommand
1935 $ cd malicious-proxycommand
1936 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1936 $ echo 's = [hg]ssh://-oProxyCommand=touch owned@example.com/path' > .hgsub
1937 $ hg ci -m 'owned username'
1937 $ hg ci -m 'owned username'
1938 $ cd ..
1938 $ cd ..
1939 $ rm -r malicious-proxycommand-clone
1939 $ rm -r malicious-proxycommand-clone
1940 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1940 $ hg clone malicious-proxycommand malicious-proxycommand-clone
1941 updating to branch default
1941 updating to branch default
1942 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1942 abort: potentially unsafe url: 'ssh://-oProxyCommand=touch owned@example.com/path' (in subrepository "s")
1943 [255]
1943 [255]
1944
1945 Test convert subrepositories including merge (issue5526):
1946
1947 $ hg init tconv
1948 $ hg convert --config extensions.convert= -q t/s tconv/s
1949 $ hg convert --config extensions.convert= -q t/s/ss tconv/s/ss
1950 $ hg convert --config extensions.convert= -q t/t tconv/t
1951
1952 convert shouldn't fail because of pseudo filenode:
1953
1954 $ hg convert --config extensions.convert= t tconv
1955 scanning source...
1956 sorting...
1957 converting...
1958 17 0
1959 16 1
1960 15 2
1961 14 3
1962 13 4
1963 12 5
1964 11 6
1965 10 7
1966 9 8
1967 8 9
1968 7 10
1969 6 11
1970 5 12
1971 4 13
1972 3 rm2
1973 2 phasecheck4
1974 1 16
1975 0 branch before subrepo add
1976
1977 converted .hgsubstate should point to valid nodes:
1978
1979 $ hg up -R tconv 9
1980 3 files updated, 0 files merged, 0 files removed, 0 files unresolved
1981 $ cat tconv/.hgsubstate
1982 fc627a69481fcbe5f1135069e8a3881c023e4cf5 s
1983 60ca1237c19474e7a3978b0dc1ca4e6f36d51382 t
General Comments 0
You need to be logged in to leave comments. Login now