Bram,
here is a patch, containing an :undorecover command that has been
requested before. If you apply this patch and use :undorecover on
existing undofiles, it will simply read in the undo structure.
But by itself this isn't really useful, as one probably ends up with
only many small pieces of the original file and most probably not the
complete file contents. But at least one can traverse the undo structure
and see if one can at least recover parts of the file.
Newly written undofiles will therefore at least contain one complete
buffer content at the time the undofile is written and no complete
buffer content has been recorded yet. When using those undofiles
together with :undorecover one will jump to that state in the undo tree
and from there one can try to recover parts of the file.
But even then, if one has many undo branches, there will be most likely
branches, that are not recoverable, because the undo information does
not contains information to recover from that initial state.
Therefore, this patch also adds an 'undosavebuf' option (off by default)
which when on, will always save the complete buffer in the undo tree
whenever the undofile is written. This possibly makes the undo tree (and
undofile) a lot larger, but the user should be able to recover different
states pretty easily.
Best,
Christian
--
--
You received this message from the "vim_dev" maillist.
Do not top-post! Type your reply below the text you are replying to.
For more information, visit http://www.vim.org/maillist.php
---
You received this message because you are subscribed to the Google Groups
"vim_dev" group.
To unsubscribe from this group and stop receiving emails from it, send an email
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.
diff --git a/runtime/doc/options.txt b/runtime/doc/options.txt
--- a/runtime/doc/options.txt
+++ b/runtime/doc/options.txt
@@ -7630,6 +7630,20 @@
Note that this causes the whole buffer to be stored in memory. Set
this option to a lower value if you run out of memory.
+ *'undosavebuf'* *'usb'*
+'undosavebuf' 'usb' boolean (default off)
+ global
+ {not in Vi}
+ Save the whole buffer for undo when writing the undo file. This makes
+ recovery using undofiles work more reliable, since several buffer
+ states will be remembered in the undo file and when recovering from an
+ undofile your buffer will be at the same time it was, when writing the
+ undofile (probably when the buffer was last written). If not set, you
+ might end up with some empty lines in your buffer which are not
+ recoverable. See also |undo-recovery|
+
+ Note that this makes the undofiles larger and will also increase memory usage.
+
*'updatecount'* *'uc'*
'updatecount' 'uc' number (default: 200)
global
diff --git a/runtime/doc/undo.txt b/runtime/doc/undo.txt
--- a/runtime/doc/undo.txt
+++ b/runtime/doc/undo.txt
@@ -14,6 +14,7 @@
4. Undo branches |undo-branches|
5. Undo persistence |undo-persistence|
6. Remarks about undo |undo-remarks|
+7. Undo recovery |undo-recovery|
==============================================================================
1. Undo and redo commands *undo-commands*
@@ -403,4 +404,32 @@
first put, and repeat the put command for the second register. Repeat the
'u.' until you got what you want.
+==============================================================================
+7. Undo recovery *undo-recovery*
+
+:undorecovery {file}
+
+ Read undo history from {file} and try to recover the buffer
+ contents. In case you have lost your buffer, you can try to
+ recover from the undofile.
+
+ If the undo history from {file} contains the complete buffer
+ contents from any time before (for example by reloading the
+ buffer, see also the 'undoreload' setting), the buffer will
+ be restored to that particular state and from there you can walk
+ forward and backward in the undo tree.
+
+ If the {file} does not contain a complete buffer content
+ anywhere in the tree, you'll be left with a buffer containing
+ only empty lines and by walking through the undo tree you can
+ at least partially recover the buffer contents. You can use
+ this command to gain an overview of the saved undo tree in
+ your buffer: >
+
+ :while changenr() > 1 | exe ":norm! g-"| sleep 500m |redraw!|endw
+<
+ Note in that case, you might not be able to recover the
+ complete buffer contents. Consider setting the 'undosavebuf'
+ option.
+
vim:tw=78:ts=8:ft=help:norl:
diff --git a/src/eval.c b/src/eval.c
--- a/src/eval.c
+++ b/src/eval.c
@@ -18704,6 +18704,7 @@
dict_add_nr_str(dict, "seq_cur", curbuf->b_u_seq_cur, NULL);
dict_add_nr_str(dict, "time_cur", (long)curbuf->b_u_time_cur, NULL);
dict_add_nr_str(dict, "save_cur", (long)curbuf->b_u_save_nr_cur, NULL);
+ dict_add_nr_str(dict, "whole_buf", (long)curbuf->b_u_save_buf, NULL);
list = list_alloc();
if (list != NULL)
diff --git a/src/ex_cmds.c b/src/ex_cmds.c
--- a/src/ex_cmds.c
+++ b/src/ex_cmds.c
@@ -3614,6 +3614,7 @@
if (u_savecommon(0, curbuf->b_ml.ml_line_count + 1, 0, TRUE)
== FAIL)
goto theend;
+ curbuf->b_u_save_buf = curbuf->b_u_seq_cur;
u_unchanged(curbuf);
buf_freeall(curbuf, BFA_KEEP_UNDO);
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1007,6 +1007,8 @@
TRLBAR|CMDWIN),
EX(CMD_undolist, "undolist", ex_undolist,
TRLBAR|CMDWIN),
+EX(CMD_undorecover, "undorecover", ex_undorecover,
+ NEEDARG|FILE1),
EX(CMD_unabbreviate, "unabbreviate", ex_abbreviate,
EXTRA|TRLBAR|NOTRLCOM|USECTRLV|CMDWIN),
EX(CMD_unhide, "unhide", ex_buffer_all,
diff --git a/src/ex_docmd.c b/src/ex_docmd.c
--- a/src/ex_docmd.c
+++ b/src/ex_docmd.c
@@ -254,6 +254,7 @@
#endif
#ifndef FEAT_PERSISTENT_UNDO
# define ex_rundo ex_ni
+# define ex_undorecover ex_ni
# define ex_wundo ex_ni
#endif
#ifndef FEAT_LUA
@@ -325,6 +326,7 @@
#ifdef FEAT_PERSISTENT_UNDO
static void ex_wundo __ARGS((exarg_T *eap));
static void ex_rundo __ARGS((exarg_T *eap));
+static void ex_undorecover __ARGS((exarg_T *eap));
#endif
static void ex_redo __ARGS((exarg_T *eap));
static void ex_later __ARGS((exarg_T *eap));
@@ -8785,7 +8787,7 @@
exarg_T *eap UNUSED;
{
if (eap->addr_count == 1) /* :undo 123 */
- undo_time(eap->line2, FALSE, FALSE, TRUE);
+ undo_time(eap->line2, FALSE, FALSE, TRUE, FALSE);
else
u_undo(1);
}
@@ -8808,7 +8810,17 @@
char_u hash[UNDO_HASH_SIZE];
u_compute_hash(hash);
- u_read_undo(eap->arg, hash, NULL);
+ u_read_undo(eap->arg, hash, NULL, FALSE);
+}
+
+ static void
+ex_undorecover(eap)
+ exarg_T *eap;
+{
+ if (!(curbuf->b_ml.ml_flags & ML_EMPTY))
+ EMSG(_("E__: undorecovery: buffer not empty!"));
+ else
+ u_read_undo(eap->arg, NULL, NULL, TRUE);
}
#endif
@@ -8852,8 +8864,8 @@
if (*p != NUL)
EMSG2(_(e_invarg2), eap->arg);
else
- undo_time(eap->cmdidx == CMD_earlier ? -count : count,
- sec, file, FALSE);
+ undo_time(eap->cmdidx == CMD_earlier ? -count : count, sec, file,
+ FALSE, FALSE);
}
/*
diff --git a/src/fileio.c b/src/fileio.c
--- a/src/fileio.c
+++ b/src/fileio.c
@@ -2596,7 +2596,7 @@
char_u hash[UNDO_HASH_SIZE];
sha256_finish(&sha_ctx, hash);
- u_read_undo(NULL, hash, fname);
+ u_read_undo(NULL, hash, fname, FALSE);
}
#endif
diff --git a/src/normal.c b/src/normal.c
--- a/src/normal.c
+++ b/src/normal.c
@@ -8503,8 +8503,8 @@
case '+':
case '-': /* "g+" and "g-": undo or redo along the timeline */
if (!checkclearopq(oap))
- undo_time(cap->nchar == '-' ? -cap->count1 : cap->count1,
- FALSE, FALSE, FALSE);
+ undo_time(cap->nchar == '-' ? -cap->count1 : cap->count1, FALSE,
+ FALSE, FALSE, FALSE);
break;
default:
diff --git a/src/option.c b/src/option.c
--- a/src/option.c
+++ b/src/option.c
@@ -2695,6 +2695,13 @@
{"undoreload", "ur", P_NUM|P_VI_DEF,
(char_u *)&p_ur, PV_NONE,
{ (char_u *)10000L, (char_u *)0L} SCRIPTID_INIT},
+ {"undosavebuffer", "usb", P_BOOL|P_VI_DEF|P_VIM,
+#ifdef FEAT_PERSISTENT_UNDO
+ (char_u *)&p_usb, PV_NONE,
+#else
+ (char_u *)NULL, PV_NONE,
+#endif
+ {(char_u *)FALSE, (char_u *)0L} SCRIPTID_INIT},
{"updatecount", "uc", P_NUM|P_VI_DEF,
(char_u *)&p_uc, PV_NONE,
{(char_u *)200L, (char_u *)0L} SCRIPTID_INIT},
@@ -7681,7 +7688,7 @@
&& !curbufIsChanged() && curbuf->b_ml.ml_mfp != NULL)
{
u_compute_hash(hash);
- u_read_undo(NULL, hash, curbuf->b_fname);
+ u_read_undo(NULL, hash, curbuf->b_fname, FALSE);
}
}
curbuf = save_curbuf;
diff --git a/src/option.h b/src/option.h
--- a/src/option.h
+++ b/src/option.h
@@ -841,6 +841,9 @@
EXTERN long p_ul; /* 'undolevels' */
EXTERN long p_ur; /* 'undoreload' */
EXTERN long p_uc; /* 'updatecount' */
+#ifdef FEAT_PERSISTENT_UNDO
+EXTERN int p_usb; /* undosavebuffer */
+#endif
EXTERN long p_ut; /* 'updatetime' */
#if defined(FEAT_WINDOWS) || defined(FEAT_FOLDING)
EXTERN char_u *p_fcs; /* 'fillchar' */
diff --git a/src/proto/undo.pro b/src/proto/undo.pro
--- a/src/proto/undo.pro
+++ b/src/proto/undo.pro
@@ -9,10 +9,10 @@
void u_compute_hash __ARGS((char_u *hash));
char_u *u_get_undo_file_name __ARGS((char_u *buf_ffname, int reading));
void u_write_undo __ARGS((char_u *name, int forceit, buf_T *buf, char_u *hash));
-void u_read_undo __ARGS((char_u *name, char_u *hash, char_u *orig_name));
+void u_read_undo __ARGS((char_u *name, char_u *hash, char_u *orig_name, int force));
void u_undo __ARGS((int count));
void u_redo __ARGS((int count));
-void undo_time __ARGS((long step, int sec, int file, int absolute));
+void undo_time __ARGS((long step, int sec, int file, int absolute, int do_recover));
void u_sync __ARGS((int force));
void ex_undolist __ARGS((exarg_T *eap));
void ex_undojoin __ARGS((exarg_T *eap));
diff --git a/src/structs.h b/src/structs.h
--- a/src/structs.h
+++ b/src/structs.h
@@ -1466,6 +1466,7 @@
int b_u_synced; /* entry lists are synced */
long b_u_seq_last; /* last used undo sequence number */
long b_u_save_nr_last; /* counter for last file write */
+ long b_u_save_buf; /* counter for when buffer was completly saved in undo tree */
long b_u_seq_cur; /* hu_seq of header below which we are now */
time_t b_u_time_cur; /* uh_time of header below which we are now */
long b_u_save_nr_cur; /* file write nr after which we are now */
diff --git a/src/undo.c b/src/undo.c
--- a/src/undo.c
+++ b/src/undo.c
@@ -88,7 +88,7 @@
static u_entry_T *u_get_headentry __ARGS((void));
static void u_getbot __ARGS((void));
static void u_doit __ARGS((int count));
-static void u_undoredo __ARGS((int undo));
+static void u_undoredo __ARGS((int undo, int do_recover));
static void u_undo_end __ARGS((int did_undo, int absolute));
static void u_add_time __ARGS((char_u *buf, size_t buflen, time_t tt));
static void u_freeheader __ARGS((buf_T *buf, u_header_T *uhp, u_header_T **uhpp));
@@ -716,6 +716,7 @@
/* extra fields for header */
# define UF_LAST_SAVE_NR 1
+# define UF_SAVE_WHOLE_BUF 2
/* extra fields for uhp */
# define UHP_SAVE_NR 1
@@ -970,6 +971,9 @@
putc(4, fp);
putc(UF_LAST_SAVE_NR, fp);
put_bytes(fp, (long_u)buf->b_u_save_nr_last, 4);
+ putc(4, fp);
+ putc(UF_SAVE_WHOLE_BUF, fp);
+ put_bytes(fp, (long_u)buf->b_u_save_buf, 4);
putc(0, fp); /* end marker */
@@ -1428,6 +1432,15 @@
goto theend;
}
+ if (p_usb || (buf->b_u_save_buf == 0 && (p_ur < 0
+ || buf->b_ml.ml_line_count <= p_ur)))
+ {
+ /* store buffer content in the undo header */
+ u_sync(FALSE);
+ u_savecommon(0, buf->b_ml.ml_line_count + 1, 0, TRUE);
+ buf->b_u_save_buf = buf->b_u_seq_cur;
+ }
+
fd = mch_open((char *)file_name,
O_CREAT|O_EXTRA|O_WRONLY|O_EXCL|O_NOFOLLOW, perm);
if (fd < 0)
@@ -1573,10 +1586,11 @@
* "hash[UNDO_HASH_SIZE]" must be the hash value of the buffer text.
*/
void
-u_read_undo(name, hash, orig_name)
+u_read_undo(name, hash, orig_name, recover)
char_u *name;
char_u *hash;
char_u *orig_name;
+ int recover; /* :undorecover */
{
char_u *file_name;
FILE *fp;
@@ -1589,6 +1603,7 @@
long old_header_seq, new_header_seq, cur_header_seq;
long seq_last, seq_cur;
long last_save_nr = 0;
+ long last_save_buf = 0;
short old_idx = -1, new_idx = -1, cur_idx = -1;
long num_read_uhps = 0;
time_t seq_time;
@@ -1694,8 +1709,8 @@
goto error;
}
line_count = (linenr_T)get4c(fp);
- if (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0
- || line_count != curbuf->b_ml.ml_line_count)
+ if (!recover && (memcmp(hash, read_hash, UNDO_HASH_SIZE) != 0
+ || line_count != curbuf->b_ml.ml_line_count))
{
if (p_verbose > 0 || name != NULL)
{
@@ -1746,6 +1761,9 @@
case UF_LAST_SAVE_NR:
last_save_nr = get4c(fp);
break;
+ case UF_SAVE_WHOLE_BUF:
+ last_save_buf = get4c(fp);
+ break;
default:
/* field not supported, skip */
while (--len >= 0)
@@ -1877,6 +1895,7 @@
curbuf->b_u_time_cur = seq_time;
curbuf->b_u_save_nr_last = last_save_nr;
curbuf->b_u_save_nr_cur = last_save_nr;
+ curbuf->b_u_save_buf = last_save_buf;
curbuf->b_u_synced = TRUE;
vim_free(uhp_table);
@@ -1889,6 +1908,28 @@
u_check(TRUE);
#endif
+ if (recover)
+ {
+ if (!undo_allowed())
+ {
+ EMSG(_("EXXX: Undorecovery not allowed!"));
+ goto theend;
+ }
+
+ /* add some empty lines */
+ for (i=0; i < line_count; i++)
+ ml_append(0, (char_u *)"", 0, TRUE);
+ appended_lines(0, line_count);
+ if (curbuf->b_u_save_buf)
+ undo_time(curbuf->b_u_save_buf, FALSE, FALSE, TRUE, TRUE);
+ /* there are at least some empty lines, so one can walk through
+ * the undo tree */
+ MSG(_("Undorecovery completed."));
+#ifdef U_DEBUG
+ u_check(TRUE);
+#endif
+ }
+
if (name != NULL)
smsg((char_u *)_("Finished reading undo file %s"), file_name);
goto theend;
@@ -2002,7 +2043,7 @@
break;
}
- u_undoredo(TRUE);
+ u_undoredo(TRUE, FALSE);
}
else
{
@@ -2017,7 +2058,7 @@
break;
}
- u_undoredo(FALSE);
+ u_undoredo(FALSE, FALSE);
/* Advance for next redo. Set "newhead" when at the end of the
* redoable changes. */
@@ -2039,11 +2080,12 @@
* "sec" must be FALSE then.
*/
void
-undo_time(step, sec, file, absolute)
+undo_time(step, sec, file, absolute, do_recover)
long step;
int sec;
int file;
int absolute;
+ int do_recover;
{
long target;
long closest;
@@ -2059,6 +2101,7 @@
int dofile = file;
int above = FALSE;
int did_undo = TRUE;
+ int did_recover = FALSE;
/* First make sure the current undoable change is synced. */
if (curbuf->b_u_synced == FALSE)
@@ -2287,10 +2330,19 @@
|| (uhp->uh_seq == target && !above))
break;
curbuf->b_u_curhead = uhp;
- u_undoredo(TRUE);
+ /* don't actually undo anything in the recovery case */
+ u_undoredo(TRUE, do_recover);
+ if (do_recover)
+ did_recover = TRUE;
uhp->uh_walk = nomark; /* don't go back down here */
}
+ if (did_recover)
+ do_recover = FALSE;
+
+ if (do_recover && curbuf->b_u_curhead == NULL)
+ /* did not do undo yet, just redo the current leaf */
+ curbuf->b_u_curhead = uhp;
/*
* And now go down the tree (redo), branching off where needed.
*/
@@ -2346,7 +2398,9 @@
break;
}
- u_undoredo(FALSE);
+ u_undoredo(FALSE, do_recover);
+ if (do_recover)
+ u_newcount--;
/* Advance "curhead" to below the header we last used. If it
* becomes NULL then we need to set "newhead" to this leaf. */
@@ -2380,8 +2434,9 @@
* When "undo" is TRUE we go up in the tree, when FALSE we go down.
*/
static void
-u_undoredo(undo)
+u_undoredo(undo, do_recover)
int undo;
+ int do_recover; /* recover buffer contents from undo file */
{
char_u **newarray = NULL;
linenr_T oldsize;
@@ -2427,14 +2482,30 @@
curbuf->b_op_end.lnum = 0;
curbuf->b_op_end.col = 0;
+ if (do_recover)
+ {
+ /* Safety check */
+ uep = curhead->uh_entry;
+ while (uep != NULL)
+ {
+ for (i = 0; i < uep->ue_size; i++)
+ {
+ /* add empty lines */
+ if (i + uep->ue_top >= curbuf->b_ml.ml_line_count)
+ ml_append(i + uep->ue_top, (char_u*)"", 0, TRUE);
+ }
+ uep = uep->ue_next;
+ }
+ }
+
for (uep = curhead->uh_entry; uep != NULL; uep = nuep)
{
top = uep->ue_top;
bot = uep->ue_bot;
if (bot == 0)
bot = curbuf->b_ml.ml_line_count + 1;
- if (top > curbuf->b_ml.ml_line_count || top >= bot
- || bot > curbuf->b_ml.ml_line_count + 1)
+ if (top > curbuf->b_ml.ml_line_count || top >= bot ||
+ bot > curbuf->b_ml.ml_line_count + 1)
{
#ifdef FEAT_AUTOCMD
unblock_autocmds();
@@ -2529,9 +2600,11 @@
ml_replace((linenr_T)1, uep->ue_array[i], TRUE);
else
ml_append(lnum, uep->ue_array[i], (colnr_T)0, FALSE);
- vim_free(uep->ue_array[i]);
+ if (!do_recover) /* don't free yet */
+ vim_free(uep->ue_array[i]);
}
- vim_free((char_u *)uep->ue_array);
+ if (!do_recover) /* don't free yet */
+ vim_free((char_u *)uep->ue_array);
}
/* adjust marks */
@@ -2557,9 +2630,19 @@
u_newcount += newsize;
u_oldcount += oldsize;
- uep->ue_size = oldsize;
- uep->ue_array = newarray;
- uep->ue_bot = top + newsize + 1;
+
+ if (!do_recover)
+ {
+ uep->ue_size = oldsize;
+ uep->ue_array = newarray;
+ uep->ue_bot = top + newsize + 1;
+ }
+ else
+ {
+ for (i=0; i < oldsize; i++)
+ vim_free(newarray[i]);
+ vim_free(newarray);
+ }
/*
* insert this entry in front of the new entry list
@@ -3103,6 +3186,9 @@
uhap = uhap->uh_alt_next.ptr)
uhap->uh_next.ptr = uhp->uh_next.ptr;
+ if (buf->b_u_save_buf == uhp->uh_seq)
+ buf->b_u_save_buf = 0;
+
u_freeentries(buf, uhp, uhpp);
}