patch 9.0.1686: undotree() only works for the current buffer

Problem:    undotree() only works for the current buffer
Solution:   Add an optional "buffer number" parameter to undotree().  If
            omitted, use the current buffer for backwards compatibility.

closes: #4001
closes: #12292

Signed-off-by: Christian Brabandt <cb@256bit.org>
Co-authored-by: zeertzjq <zeertzjq@outlook.com>
Co-authored-by: Devin J. Pohly <djpohly@gmail.com>
diff --git a/src/evalfunc.c b/src/evalfunc.c
index 8970ac7..0903fa8 100644
--- a/src/evalfunc.c
+++ b/src/evalfunc.c
@@ -2798,7 +2798,7 @@
 			ret_string,	    f_typename},
     {"undofile",	1, 1, FEARG_1,	    arg1_string,
 			ret_string,	    f_undofile},
-    {"undotree",	0, 0, 0,	    NULL,
+    {"undotree",	0, 1, FEARG_1,	    arg1_buffer,
 			ret_dict_any,	    f_undotree},
     {"uniq",		1, 3, FEARG_1,	    arg13_sortuniq,
 			ret_first_arg,	    f_uniq},
diff --git a/src/testdir/test_undo.vim b/src/testdir/test_undo.vim
index 2529e21..461b28f 100644
--- a/src/testdir/test_undo.vim
+++ b/src/testdir/test_undo.vim
@@ -93,6 +93,53 @@
   endfor
 endfunc
 
+func Test_undotree_bufnr()
+  new
+  let buf1 = bufnr()
+
+  normal! Aabc
+  set ul=100
+
+  " Save undo tree without bufnr as ground truth for buffer 1
+  let d1 = undotree()
+
+  new
+  let buf2 = bufnr()
+
+  normal! Adef
+  set ul=100
+
+  normal! Aghi
+  set ul=100
+
+  " Save undo tree without bufnr as ground truth for buffer 2
+  let d2 = undotree()
+
+  " Check undotree() with bufnr argument
+  let d = undotree(buf1)
+  call assert_equal(d1, d)
+  call assert_notequal(d2, d)
+
+  let d = undotree(buf2)
+  call assert_notequal(d1, d)
+  call assert_equal(d2, d)
+
+  " Switch buffers and check again
+  wincmd p
+
+  let d = undotree(buf1)
+  call assert_equal(d1, d)
+
+  let d = undotree(buf2)
+  call assert_notequal(d1, d)
+  call assert_equal(d2, d)
+
+  " Drop created windows
+  set ul&
+  new
+  only!
+endfunc
+
 func Test_global_local_undolevels()
   new one
   set undolevels=5
diff --git a/src/undo.c b/src/undo.c
index 9e122f9..85c4670 100644
--- a/src/undo.c
+++ b/src/undo.c
@@ -3629,7 +3629,7 @@
  * Recursive.
  */
     static void
-u_eval_tree(u_header_T *first_uhp, list_T *list)
+u_eval_tree(buf_T *buf, u_header_T *first_uhp, list_T *list)
 {
     u_header_T  *uhp = first_uhp;
     dict_T	*dict;
@@ -3641,9 +3641,9 @@
 	    return;
 	dict_add_number(dict, "seq", uhp->uh_seq);
 	dict_add_number(dict, "time", (long)uhp->uh_time);
-	if (uhp == curbuf->b_u_newhead)
+	if (uhp == buf->b_u_newhead)
 	    dict_add_number(dict, "newhead", 1);
-	if (uhp == curbuf->b_u_curhead)
+	if (uhp == buf->b_u_curhead)
 	    dict_add_number(dict, "curhead", 1);
 	if (uhp->uh_save_nr > 0)
 	    dict_add_number(dict, "save", uhp->uh_save_nr);
@@ -3655,7 +3655,7 @@
 	    if (alt_list != NULL)
 	    {
 		// Recursive call to add alternate undo tree.
-		u_eval_tree(uhp->uh_alt_next.ptr, alt_list);
+		u_eval_tree(buf, uhp->uh_alt_next.ptr, alt_list);
 		dict_add_list(dict, "alt", alt_list);
 	    }
 	}
@@ -3721,28 +3721,40 @@
  #endif
 
 /*
- * "undotree()" function
+ * "undotree(expr)" function
  */
     void
 f_undotree(typval_T *argvars UNUSED, typval_T *rettv)
 {
+    typval_T	*tv = &argvars[0];
+    buf_T	*buf;
+    dict_T	*dict;
+    list_T	*list;
+
+    if (in_vim9script() && check_for_opt_buffer_arg(argvars, 0) == FAIL)
+	return;
+
+    if (tv->v_type == VAR_UNKNOWN)
+	buf = curbuf;
+    else
+	buf = tv_get_buf_from_arg(tv);
+
     if (rettv_dict_alloc(rettv) == FAIL)
 	return;
 
-    dict_T *dict = rettv->vval.v_dict;
-    list_T *list;
+    dict = rettv->vval.v_dict;
 
-    dict_add_number(dict, "synced", (long)curbuf->b_u_synced);
-    dict_add_number(dict, "seq_last", curbuf->b_u_seq_last);
-    dict_add_number(dict, "save_last", curbuf->b_u_save_nr_last);
-    dict_add_number(dict, "seq_cur", curbuf->b_u_seq_cur);
-    dict_add_number(dict, "time_cur", (long)curbuf->b_u_time_cur);
-    dict_add_number(dict, "save_cur", curbuf->b_u_save_nr_cur);
+    dict_add_number(dict, "synced", (long)buf->b_u_synced);
+    dict_add_number(dict, "seq_last", buf->b_u_seq_last);
+    dict_add_number(dict, "save_last", buf->b_u_save_nr_last);
+    dict_add_number(dict, "seq_cur", buf->b_u_seq_cur);
+    dict_add_number(dict, "time_cur", (long)buf->b_u_time_cur);
+    dict_add_number(dict, "save_cur", buf->b_u_save_nr_cur);
 
     list = list_alloc();
     if (list != NULL)
     {
-	u_eval_tree(curbuf->b_u_oldhead, list);
+	u_eval_tree(buf, buf->b_u_oldhead, list);
 	dict_add_list(dict, "entries", list);
     }
 }
diff --git a/src/version.c b/src/version.c
index 7134218..f4c73e7 100644
--- a/src/version.c
+++ b/src/version.c
@@ -696,6 +696,8 @@
 static int included_patches[] =
 {   /* Add new patch number below this line */
 /**/
+    1686,
+/**/
     1685,
 /**/
     1684,