let &undofile = _uf
endfunc
+" Test that an undo file with alternate branches round-trips: the tree
+" structure and the text at every sequence number survive :wundo + :rundo.
+func Test_undofile_branches()
+ CheckFeature persistent_undo
+ let save_ul = &undolevels
+ defer execute('let &undolevels = ' .. save_ul)
+ new Xubranches.txt
+ setl noswapfile
+ set ul=100
+ call setline(1, 'a')
+ for i in range(5)
+ let &undolevels = &undolevels
+ call setline(1, 'main' .. i)
+ endfor
+ " create two alternate branches
+ silent undo 3
+ let &undolevels = &undolevels
+ call setline(1, 'branch-a')
+ silent undo 2
+ let &undolevels = &undolevels
+ call setline(1, 'branch-b')
+
+ write
+ defer delete('Xubranches.txt')
+ wundo! Xubranches.undo
+ defer delete('Xubranches.undo')
+ let tree_before = undotree()
+ " remember the text at every undo state
+ let texts = {}
+ for seq in range(1, tree_before.seq_last)
+ exe 'silent undo ' .. seq
+ let texts[seq] = getline(1)
+ endfor
+ bwipe!
+
+ edit Xubranches.txt
+ setl noswapfile
+ rundo Xubranches.undo
+ let tree_after = undotree()
+ call assert_equal(tree_before.seq_last, tree_after.seq_last)
+ " every field of the entries, including times and save numbers, must
+ " round-trip through the undo file exactly
+ call assert_equal(tree_before.entries, tree_after.entries)
+ for [seq, text] in items(texts)
+ exe 'silent undo ' .. seq
+ call assert_equal(text, getline(1), 'text at undo state ' .. seq)
+ endfor
+
+ bwipe!
+endfunc
+
+" Test that a duplicated sequence number in an undo file is detected.
+func Test_undofile_duplicate_seq()
+ CheckFeature persistent_undo
+ let save_ul = &undolevels
+ defer execute('let &undolevels = ' .. save_ul)
+ new Xudupseq.txt
+ setl noswapfile
+ set ul=100
+ call setline(1, 'one')
+ let &undolevels = &undolevels
+ call setline(1, 'two')
+ let &undolevels = &undolevels
+ call setline(1, 'three')
+ write
+ defer delete('Xudupseq.txt')
+ wundo! Xudupseq.undo
+ defer delete('Xudupseq.undo')
+
+ " Overwrite the uh_seq of the second header with that of the first. A
+ " header starts with the magic bytes 0x5f 0xd0, followed by four 4-byte
+ " header references and then the 4-byte uh_seq. Require the references
+ " and uh_seq to be small numbers, so that a timestamp that happens to
+ " contain the magic bytes is not mistaken for a header.
+ let blob = readfile('Xudupseq.undo', 'B')
+ let headers = []
+ for i in range(len(blob) - 22)
+ if blob[i] == 0x5f && blob[i + 1] == 0xd0
+ let ok = v:true
+ for field in range(5)
+ let off = i + 2 + field * 4
+ if blob[off] != 0 || blob[off + 1] != 0 || blob[off + 2] != 0
+ \ || blob[off + 3] > 8
+ let ok = v:false
+ break
+ endif
+ endfor
+ if ok
+ call add(headers, i)
+ endif
+ endif
+ endfor
+ call assert_true(len(headers) >= 2, 'found undo file headers')
+ let first = headers[0] + 18
+ let second = headers[1] + 18
+ " Check that the detected headers are the intended ones before patching
+ " any bytes: the headers are written oldest first, so the first two carry
+ " sequence numbers 1 and 2.
+ call assert_equal(0z00000001, blob[first : first + 3])
+ call assert_equal(0z00000002, blob[second : second + 3])
+ let blob[second : second + 3] = blob[first : first + 3]
+ call writefile(blob, 'Xudupseq.undo')
+ call assert_fails('rundo Xudupseq.undo', 'E825:')
+
+ bwipe!
+endfunc
+
+" Test reading an undo file with zero undo headers, which is written when
+" only the line for the "U" command is saved, e.g. with 'undolevels' -1.
+func Test_undofile_zero_headers()
+ CheckFeature persistent_undo
+ let save_ul = &undolevels
+ defer execute('let &undolevels = ' .. save_ul)
+ " The buffer must not collect any undo header, so create the file on disk
+ " directly and only change the buffer with 'undolevels' already negative.
+ call writefile(['hello', 'world'], 'Xuzero.txt', 'D')
+ set undolevels=-1
+ edit Xuzero.txt
+ normal! x
+ wundo! Xuzero.undo
+ defer delete('Xuzero.undo')
+ bwipe!
+
+ " Make the same change so that the buffer text matches the hash stored in
+ " the undo file, then read the undo file back.
+ edit Xuzero.txt
+ normal! x
+ let v:errmsg = ''
+ " a silent rejection of the undo file gives a warning, not an error
+ let v:warningmsg = ''
+ rundo Xuzero.undo
+ call assert_equal('', v:errmsg)
+ call assert_equal('', v:warningmsg)
+ normal! U
+ call assert_equal('hello', getline(1))
+
+ bwipe!
+endfunc
+
" vim: shiftwidth=2 sts=2 expandtab
vim_free(file_name);
}
+/*
+ * Compare undo headers on the sequence number, for sorting uhp_table in
+ * u_read_undo().
+ */
+ static int
+uhp_seq_cmp(const void *v1, const void *v2)
+{
+ const u_header_T *u1 = *(u_header_T **)v1;
+ const u_header_T *u2 = *(u_header_T **)v2;
+
+ return u1->uh_seq == u2->uh_seq ? 0 : u1->uh_seq > u2->uh_seq ? 1 : -1;
+}
+
+/*
+ * Find the header with sequence number "seq" in "uhp_table", which has
+ * "num_head" entries and is sorted on uh_seq.
+ * Return the table index of the header or -1 when not found.
+ */
+ static long
+uhp_table_find(u_header_T **uhp_table, long num_head, long seq)
+{
+ long lo = 0;
+ long hi = num_head - 1;
+
+ while (lo <= hi)
+ {
+ long mid = lo + (hi - lo) / 2;
+
+ if (uhp_table[mid]->uh_seq < seq)
+ lo = mid + 1;
+ else if (uhp_table[mid]->uh_seq > seq)
+ hi = mid - 1;
+ else
+ return mid;
+ }
+ return -1;
+}
+
/*
* Load the undo tree from an undo file.
* If "name" is not NULL use it as the undo file name. This also means being
long old_header_seq, new_header_seq, cur_header_seq;
long seq_last, seq_cur;
long last_save_nr = 0;
- short old_idx = -1, new_idx = -1, cur_idx = -1;
+ long old_idx = -1, new_idx = -1, cur_idx = -1;
long num_read_uhps = 0;
time_t seq_time;
- int i, j;
+ long i;
int c;
u_header_T *uhp;
u_header_T **uhp_table = NULL;
# define SET_FLAG(j)
# endif
- // We have put all of the headers into a table. Now we iterate through the
- // table and swizzle each sequence number we have stored in uh_*_seq into
- // a pointer corresponding to the header with that sequence number.
- for (i = 0; i < num_head; i++)
+ // We have put all of the headers into a table. Each header stores the
+ // sequence numbers of the headers it links to; resolve those into
+ // pointers. Sort the table on uh_seq once, so that every lookup is a
+ // binary search instead of a linear scan, which would be quadratic
+ // overall. Every entry is non-NULL: a header that failed to
+ // unserialize or a count mismatch was an error above.
+ if (num_head > 0)
+ qsort(uhp_table, (size_t)num_head, sizeof(u_header_T *), uhp_seq_cmp);
+
+ // In the sorted table two headers with the same uh_seq are neighbours.
+ for (i = 0; i < num_head - 1; i++)
{
- uhp = uhp_table[i];
- if (uhp == NULL)
- continue;
- for (j = 0; j < num_head; j++)
- if (uhp_table[j] != NULL && i != j
- && uhp_table[i]->uh_seq == uhp_table[j]->uh_seq)
- {
- corruption_error("duplicate uh_seq", file_name);
- goto error;
- }
+ if (uhp_table[i]->uh_seq == uhp_table[i + 1]->uh_seq)
{
- int seq = uhp->uh_next.seq;
- uhp->uh_next.ptr = NULL;
- for (j = 0; j < num_head; j++)
- if (uhp_table[j] != NULL && i != j
- && uhp_table[j]->uh_seq == seq)
- {
- uhp->uh_next.ptr = uhp_table[j];
- SET_FLAG(j);
- break;
- }
- }
- {
- int seq = uhp->uh_prev.seq;
- uhp->uh_prev.ptr = NULL;
- for (j = 0; j < num_head; j++)
- if (uhp_table[j] != NULL && i != j
- && uhp_table[j]->uh_seq == seq)
- {
- uhp->uh_prev.ptr = uhp_table[j];
- SET_FLAG(j);
- break;
- }
- }
- {
- int seq = uhp->uh_alt_next.seq;
- uhp->uh_alt_next.ptr = NULL;
- for (j = 0; j < num_head; j++)
- if (uhp_table[j] != NULL && i != j
- && uhp_table[j]->uh_seq == seq)
- {
- uhp->uh_alt_next.ptr = uhp_table[j];
- SET_FLAG(j);
- break;
- }
- }
- {
- int seq = uhp->uh_alt_prev.seq;
- uhp->uh_alt_prev.ptr = NULL;
- for (j = 0; j < num_head; j++)
- if (uhp_table[j] != NULL && i != j
- && uhp_table[j]->uh_seq == seq)
- {
- uhp->uh_alt_prev.ptr = uhp_table[j];
- SET_FLAG(j);
- break;
- }
+ corruption_error("duplicate uh_seq", file_name);
+ goto error;
}
+ }
+
+ // Resolve the sequence number "link".seq into a pointer to the header
+ // with that number. A number that does not match any header, including
+ // zero (written for a NULL pointer) and the own sequence number of the
+ // header "hidx", resolves to NULL.
+# define SWIZZLE_SEQ(link, hidx) \
+ do { \
+ long fidx = uhp_table_find(uhp_table, num_head, (link).seq); \
+ \
+ if (fidx >= 0 && fidx != (hidx)) \
+ { \
+ (link).ptr = uhp_table[fidx]; \
+ SET_FLAG(fidx); \
+ } \
+ else \
+ (link).ptr = NULL; \
+ } while (0)
+
+ for (i = 0; i < num_head; i++)
+ {
+ uhp = uhp_table[i];
+ SWIZZLE_SEQ(uhp->uh_next, i);
+ SWIZZLE_SEQ(uhp->uh_prev, i);
+ SWIZZLE_SEQ(uhp->uh_alt_next, i);
+ SWIZZLE_SEQ(uhp->uh_alt_prev, i);
if (old_header_seq > 0 && old_idx < 0 && uhp->uh_seq == old_header_seq)
{
old_idx = i;
SET_FLAG(i);
}
}
+# undef SWIZZLE_SEQ
// Now that we have read the undo info successfully, free the current undo
// info and use the info from the file.
static int included_patches[] =
{ /* Add new patch number below this line */
+/**/
+ 935,
/**/
934,
/**/