diff --git a/src/ChangeLog b/src/ChangeLog
index f4246b70fa33ad21d97b8505312a40b66c688a2d..f0f2733df7d0a6b2147d4e7d7541479e61fa6ee9 100644
--- a/src/ChangeLog
+++ b/src/ChangeLog
@@ -1,3 +1,25 @@
+2008-11-05  Mark D. Baushke  <mdb@gnu.org>
+
+	*diff.c (diff): Use SEND_FORCE on files when a -k option is used
+	with diff to avoid client/server not finding the requested file.
+	* sanity.sh (keyword): Add tests for this.
+
+	* checkout.c (checkout): For multiroot checkout/update operations,
+	do not assume that the static join_rev1 and join_rev2 strings will
+	not be seen again with another repository.
+	* update.c (update): Ditto.
+	* commit.c (commit): For multiroot commits with -F logmessage
+	do not generate "cannot specify both a message and a log file"
+	errors. Track the use of -m as an argument explicitly.
+	(finaladd): API change to pass the saved_message.
+	(check_fileproc, commit_fileproc, commit_filesdoneproc): Pass the
+	saved_message via the callerdat block.
+	(commit_direntproc): Ditto.
+	* sanity.sh (multiroot-{commit-2,update-join-{1,2},admin-{1,2,3,4}}):
+	Do commits with -F logmessage to test above and also look at other
+	broken behavior which needs to be fixed. There are some FIXME
+	tests here which should be addressed.
+
 2008-10-15  Derek R. Price  <derek@ximbiot.com>
 
 	* checkout.c (findslash): Declare inputs const.  Use ISSLASH().
diff --git a/src/checkout.c b/src/checkout.c
index 0f408341f7fe172b9125fdbeb8dc59b32f23f918..ddf1b2e1ab2564a57b2c8f6d1ef61f2d24406401 100644
--- a/src/checkout.c
+++ b/src/checkout.c
@@ -128,6 +128,7 @@ checkout (int argc, char **argv)
     int shorten = -1;
     char *where = NULL;
     const char *valid_options;
+    int jrev_count = 0;
     const char *const *valid_usage;
     char *join_orig1, *join_orig2;
 
@@ -235,15 +236,15 @@ checkout (int argc, char **argv)
 		checkout_prune_dirs = 1;
 		break;
 	    case 'j':
-		if (join_rev2 || join_date2)
+		jrev_count++;
+		if (jrev_count > 2)
 		    error (1, 0, "only two -j options can be specified");
-		if (join_rev1 || join_date1)
-		{
+		if (jrev_count == 2) {
 		    if (join_orig2) free (join_orig2);
 		    join_orig2 = xstrdup (optarg);
 		    parse_tagdate (&join_rev2, &join_date2, optarg);
 		}
-		else
+		else if (jrev_count == 1)
 		{
 		    if (join_orig1) free (join_orig1);
 		    join_orig1 = xstrdup (optarg);
diff --git a/src/commit.c b/src/commit.c
index 5e5fbabc9db6255b1a8a3759b0cdd89de6f09ebc..0397f754be4b941535ec859a5a97f7154baef740 100644
--- a/src/commit.c
+++ b/src/commit.c
@@ -60,7 +60,7 @@ static int commit_filesdoneproc (void *callerdat, int err,
                                  const char *repository,
 				 const char *update_dir, List *entries);
 static int finaladd (struct file_info *finfo, char *revision, char *tag,
-		     char *options);
+		     char *options, char *msg);
 static int findmaxrev (Node * p, void *closure);
 static int lock_RCS (const char *user, RCSNode *rcs, const char *rev,
                      const char *repository);
@@ -85,6 +85,10 @@ struct master_lists
     List *cilist;			/* list with commit_info structs */
 };
 
+struct commit_data
+{
+    char *saved_message;
+};
 static int check_valid_edit = 0;
 static int force_ci = 0;
 static int got_message;
@@ -94,7 +98,6 @@ static char *write_dirtag;
 static int write_dirnonbranch;
 static char *logfile;
 static List *mulist;
-static char *saved_message;
 static time_t last_register_time;
 
 static const char *const commit_usage[] =
@@ -363,6 +366,7 @@ commit (int argc, char **argv)
     int c;
     int err = 0;
     int local = 0;
+    struct commit_data commit_data;
 
 #ifdef CLIENT_SUPPORT
     int flags;
@@ -375,6 +379,8 @@ commit (int argc, char **argv)
     const char short_options[] = COMMIT_OPTIONS;
 #endif /* SERVER_SUPPORT */
 
+    memset(&commit_data, 0, sizeof commit_data);
+
     if (argc == -1)
 	usage (commit_usage);
 
@@ -423,13 +429,13 @@ commit (int argc, char **argv)
 #else
 		use_editor = 0;
 #endif
-		if (saved_message)
+		if (commit_data.saved_message)
 		{
-		    free (saved_message);
-		    saved_message = NULL;
+		    free (commit_data.saved_message);
+		    commit_data.saved_message = NULL;
 		}
 
-		saved_message = xstrdup (optarg);
+		commit_data.saved_message = xstrdup (optarg);
 		break;
 	    case 'r':
 		if (saved_tag)
@@ -481,10 +487,11 @@ commit (int argc, char **argv)
     {
 	size_t size = 0, len;
 
-	if (saved_message)
+	if (commit_data.saved_message)
 	    error (1, 0, "cannot specify both a message and a log file");
 
-	get_file (logfile, logfile, "r", &saved_message, &size, &len);
+	get_file (logfile, logfile, "r", &commit_data.saved_message,
+		  &size, &len);
     }
 
 #ifdef CLIENT_SUPPORT
@@ -553,10 +560,12 @@ commit (int argc, char **argv)
 	 * The protocol is designed this way.  This is a feature.
 	 */
 	if (use_editor)
-	    do_editor (NULL, &saved_message, NULL, find_args.ulist);
-
+	    do_editor (NULL, &commit_data.saved_message, NULL,
+		       find_args.ulist);
+	
 	/* We always send some sort of message, even if empty.  */
-	option_with_arg ("-m", saved_message ? saved_message : "");
+	option_with_arg ("-m", commit_data.saved_message
+			 ? commit_data.saved_message : "");
 
 	/* OK, now process all the questionable files we have been saving
 	   up.  */
@@ -646,7 +655,7 @@ commit (int argc, char **argv)
 
 	send_to_server ("ci\012", 0);
 	err = get_responses_and_close ();
-	if (err != 0 && use_editor && saved_message != NULL)
+	if (err != 0 && use_editor && commit_data.saved_message != NULL)
 	{
 	    /* If there was an error, don't nuke the user's carefully
 	       constructed prose.  This is something of a kludge; a better
@@ -664,8 +673,9 @@ commit (int argc, char **argv)
 	    if (fp == NULL)
 		error (1, 0, "cannot create temporary file %s",
 		       fname ? fname : "(null)");
-	    if (fwrite (saved_message, 1, strlen (saved_message), fp)
-		!= strlen (saved_message))
+	    if (fwrite (commit_data.saved_message, 1,
+			strlen (commit_data.saved_message), fp)
+		!= strlen (commit_data.saved_message))
 		error (1, errno, "cannot write temporary file %s", fname);
 	    if (fclose (fp) < 0)
 		error (0, errno, "cannot close temporary file %s", fname);
@@ -710,7 +720,8 @@ commit (int argc, char **argv)
      * Run the recursion processor to verify the files are all up-to-date
      */
     if (start_recursion (check_fileproc, check_filesdoneproc,
-			 check_direntproc, NULL, NULL, argc, argv, local,
+			 check_direntproc, NULL, &commit_data,
+			 argc, argv, local,
 			 W_LOCAL, aflag, CVS_LOCK_NONE, NULL, 1, NULL))
 	error (1, 0, "correct above errors first!");
 
@@ -720,7 +731,8 @@ commit (int argc, char **argv)
     write_dirnonbranch = 0;
     if (noexec == 0)
 	err = start_recursion (commit_fileproc, commit_filesdoneproc,
-                               commit_direntproc, commit_dirleaveproc, NULL,
+                               commit_direntproc, commit_dirleaveproc,
+			       &commit_data,
                                argc, argv, local, W_LOCAL, aflag,
                                CVS_LOCK_WRITE, NULL, 1, NULL);
 
@@ -730,6 +742,12 @@ commit (int argc, char **argv)
     Lock_Cleanup ();
     dellist (&mulist);
 
+    if (commit_data.saved_message)
+    {
+	free (commit_data.saved_message);
+	commit_data.saved_message = NULL;
+    }
+
     /* see if we need to sleep before returning to avoid time-stamp races */
     if (!server_active && last_register_time)
     {
@@ -1381,6 +1399,7 @@ check_filesdoneproc (void *callerdat, int err, const char *repos,
     int n;
     Node *p;
     List *saved_ulist;
+    struct commit_data *commit_data = callerdat;
 
     TRACE (TRACE_FLOW, "check_filesdoneproc (%d, %s, %s, %s)",
 	   err, repos, update_dir, TRACE_BOOL (use_editor));
@@ -1413,9 +1432,10 @@ check_filesdoneproc (void *callerdat, int err, const char *repos,
      */
     got_message = 1;
     if (!server_active && use_editor)
-	do_editor (update_dir, &saved_message, repos, saved_ulist);
+	do_editor (update_dir, &commit_data->saved_message, repos,
+		   saved_ulist);
 
-    err += do_verify (&saved_message, repos, saved_ulist);
+    err += do_verify (&commit_data->saved_message, repos, saved_ulist);
 
     return err;
 }
@@ -1436,6 +1456,7 @@ commit_fileproc (void *callerdat, struct file_info *finfo)
     int err = 0;
     List *ulist, *cilist;
     struct commit_info *ci;
+    struct commit_data *commit_data = callerdat;
 
     /* Keep track of whether write_dirtag is a branch tag.
        Note that if it is a branch tag in some files and a nonbranch tag
@@ -1510,7 +1531,7 @@ commit_fileproc (void *callerdat, struct file_info *finfo)
 		free (ci->rev);
 	    ci->rev = RCS_whatbranch (finfo->rcs, ci->tag);
 	    err = Checkin ('A', finfo, ci->rev,
-			   ci->tag, ci->options, saved_message);
+			   ci->tag, ci->options, commit_data->saved_message);
 	    if (err != 0)
 	    {
 		unlockrcs (finfo->rcs);
@@ -1548,14 +1569,16 @@ commit_fileproc (void *callerdat, struct file_info *finfo)
 	}
 
 	/* XXX - an added file with symbolic -r should add tag as well */
-	err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options);
+	err = finaladd (finfo, ci->rev ? ci->rev : xrev, ci->tag, ci->options,
+			commit_data->saved_message);
+
 	if (xrev)
 	    free (xrev);
     }
     else if (ci->status == T_MODIFIED)
     {
 	err = Checkin ('M', finfo, ci->rev, ci->tag,
-		       ci->options, saved_message);
+		       ci->options, commit_data->saved_message);
 
 	(void) time (&last_register_time);
 
@@ -1567,7 +1590,7 @@ commit_fileproc (void *callerdat, struct file_info *finfo)
     }
     else if (ci->status == T_REMOVED)
     {
-	err = remove_file (finfo, ci->tag, saved_message);
+	err = remove_file (finfo, ci->tag, commit_data->saved_message);
 #ifdef SERVER_SUPPORT
 	if (server_active)
 	{
@@ -1647,6 +1670,7 @@ commit_filesdoneproc (void *callerdat, int err, const char *repository,
 {
     Node *p;
     List *ulist;
+    struct commit_data *commit_data = callerdat;
 
     assert (repository);
 
@@ -1715,7 +1739,7 @@ commit_filesdoneproc (void *callerdat, int err, const char *repository,
      * A more general solution I have been considering is calling a generic
      * "postwrite" hook from the remove write lock routine.
      */
-    Update_Logfile (repository, saved_message, NULL, ulist);
+    Update_Logfile (repository, commit_data->saved_message, NULL, ulist);
 
     return err;
 }
@@ -1966,11 +1990,12 @@ remove_file (struct file_info *finfo, char *tag, char *message)
  * Do the actual checkin for added files
  */
 static int
-finaladd (struct file_info *finfo, char *rev, char *tag, char *options)
+finaladd (struct file_info *finfo, char *rev, char *tag, char *options,
+	  char *msg)
 {
     int ret;
 
-    ret = Checkin ('A', finfo, rev, tag, options, saved_message);
+    ret = Checkin ('A', finfo, rev, tag, options, msg);
     if (ret == 0)
     {
 	char *tmp = Xasprintf ("%s/%s%s", CVSADM, finfo->file, CVSEXT_LOG);
diff --git a/src/diff.c b/src/diff.c
index e8a5394c409f4631badd9c44c0fc02d5a12f6230..5fa6fd07505dc41e7af7e3ed2d610d8aa8adc448 100644
--- a/src/diff.c
+++ b/src/diff.c
@@ -445,6 +445,8 @@ diff (int argc, char **argv)
 	if ((!suppress_bases && supported_request ("Base-diff"))
 	    || diff_rev2 || diff_date2)
 	    flags |= SEND_NO_CONTENTS;
+	else if (options[0] != '\0')
+	    flags |= SEND_FORCE;
 
 	send_files (argc, argv, local, 0, flags);
 
diff --git a/src/sanity.sh b/src/sanity.sh
index 7f268bddfc6f173044eecba322f901ff55b3d4bf..9478818e7542a0f1d8dee077e1a4e3c7707cdfc6 100755
--- a/src/sanity.sh
+++ b/src/sanity.sh
@@ -26540,6 +26540,18 @@ xx "'\$'"Log"'\$'
 	  dotest keyword-24 "cat file1" '\$'"Name:  "'\$'"
 change"
 
+	  dotest_fail keyword-25 "${testcvs} diff -kk file1" \
+"Index: file1
+===================================================================
+RCS file: ${CVSROOT_DIRNAME}/first-dir/file1,v
+retrieving revision 1\.3
+diff -r1\.3 file1
+1c1
+< \$Name\$
+---
+> \$Name:  $"
+	  dotest keyword-26 "${testcvs} diff -kkv file1" ""
+
 	  dokeep
 	  cd ../..
 	  rm -rf 1
@@ -30922,6 +30934,91 @@ new revision: 1.2; previous revision: 1.1
 ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v  <--  mod2-2/file2-2
 new revision: 1.2; previous revision: 1.1"
 
+	  echo Extra-line >> mod1-1/file1-1
+	  echo Extra-line >> mod2-2/file2-2
+	  echo 'Using a message file.' >log-message.txt
+	  dotest multiroot-commit-2 \
+"${testcvs} commit -F log-message.txt mod1-1/file1-1 mod2-2/file2-2" \
+"${CVSROOT1_DIRNAME}/mod1-1/file1-1,v  <--  mod1-1/file1-1
+new revision: 1\.3; previous revision: 1\.2
+${CVSROOT2_DIRNAME}/mod2-2/file2-2,v  <--  mod2-2/file2-2
+new revision: 1\.3; previous revision: 1\.2"
+
+	  dotest multiroot-update-join-1 \
+"${testcvs} update -j1.3 -j1.2 mod1-1/file1-1 mod2-2/file2-2" \
+"${CPROG} update: Replacing \`mod1-1/file1-1' with contents of revision 1\.2\.
+M mod1-1/file1-1
+${CPROG} update: Replacing \`mod2-2/file2-2' with contents of revision 1\.2\.
+M mod2-2/file2-2"
+	  dotest multiroot-update-join-2 \
+"${testcvs} -Q update -C mod1-1/file1-1 mod2-2/file2-2" \
+""
+	  
+	  rm log-message.txt
+	  dotest multiroot-admin-1 "${testcvs} admin -o1.3 mod1-1/file1-1" \
+"RCS file: ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+deleting revision 1.3
+done"
+	  # FIXME: For $remote operation, an error like:
+	  # cvs [admin aborted]: no such directory `mod2-2'
+	  # will happen if the CVS/Root has $CVSROOT1 in it.
+	  # If CVS is missing and there is no CVSROOT environment
+	  # variable, there is also a "No CVSROOT specified!"
+	  # error which really needs to be fixed too.
+          dotest multiroot-admin-2 \
+"${testcvs} -d ${CVSROOT2} admin -o1.3 mod2-2/file2-2" \
+"RCS file: ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+deleting revision 1.3
+done"
+
+	  if $remote; then
+	    # FIXME: Just becuase CVS/Root does not have a mod2-2
+	    # directory does not mean that recurse should print an
+	    # error.
+	    dotest multiroot-admin-3ra "${testcvs} status mod2-2/file2-2" \
+"${CPROG} \[status aborted\]: no such directory \`mod2-2'
+===================================================================
+File: file2-2          	Status: Needs Patch
+
+   Working revision:	1\.3
+   Repository revision:	1\.2	${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+   Commit Identifier:	${commitid}
+   Sticky Tag:		(none)
+   Sticky Date:		(none)
+   Sticky Options:	(none)"
+
+	    dotest_fail multiroot-admin-3rb \
+"${testcvs} update mod1-1/file1-1 mod2-2/file2-2" \
+"${CPROG} \[update aborted\]: could not find desired version 1\.3 in ${CVSROOT1_DIRNAME}/mod1-1/file1-1,v
+${CPROG} \[update aborted\]: could not find desired version 1\.3 in ${CVSROOT2_DIRNAME}/mod2-2/file2-2,v"
+
+	    # FIXME. For client/server, the removal of revisoin 1.3 is not
+	    # directly recoverable without hacking CVS/Entries here.
+	    mv mod1-1/CVS/Entries mod1-1/CVS/Entries.bad
+            grep -v file1-1 < mod1-1/CVS/Entries.bad > mod1-1/CVS/Entries
+	    mv mod2-2/CVS/Entries mod2-2/CVS/Entries.bad
+            grep -v file2-2 < mod2-2/CVS/Entries.bad > mod2-2/CVS/Entries
+	    rm mod1-1/CVS/Entries.bad mod2-2/CVS/Entries.bad
+	    rm mod1-1/file1-1 mod2-2/file2-2
+	  else
+	    dotest multiroot-admin-3 "${testcvs} status mod2-2/file2-2" \
+"===================================================================
+File: file2-2          	Status: Needs Patch
+
+   Working revision:	1\.3.*
+   Repository revision:	1\.2	${CVSROOT2_DIRNAME}/mod2-2/file2-2,v
+   Commit Identifier:	${commitid}
+   Sticky Tag:		(none)
+   Sticky Date:		(none)
+   Sticky Options:	(none)"
+	  fi
+
+	  dotest multiroot-admin-4 \
+"${testcvs} update mod1-1/file1-1 mod2-2/file2-2" \
+"U mod1-1/file1-1
+U mod2-2/file2-2"
+
+
 	  dotest multiroot-update-2 "${testcvs} update" \
 "${CPROG} update: Updating \.
 ${CPROG} update: Updating mod1-1
diff --git a/src/update.c b/src/update.c
index 6d0bfc3b67b3d36f67c33ad5291ac51f8c43cb9f..af051d513bae1d4604b4fc2dc69179b49f70de12 100644
--- a/src/update.c
+++ b/src/update.c
@@ -165,6 +165,7 @@ update (int argc, char **argv)
     int c, err;
     int local = 0;			/* recursive by default */
     int which;				/* where to look for files and dirs */
+    int jrev_count = 0;			/* multiple cvsroot w/ -j options */
     char *xjoin_rev1, *xjoin_date1,
 	 *xjoin_rev2, *xjoin_date2,
 	 *join_orig1, *join_orig2;
@@ -237,14 +238,15 @@ update (int argc, char **argv)
 		noexec = 1;		/* so no locks will be created */
 		break;
 	    case 'j':
-		if (join_orig2)
+		jrev_count++;
+		if (jrev_count > 2)
 		    error (1, 0, "only two -j options can be specified");
-		if (join_orig1)
+		if (jrev_count == 2)
 		{
 		    join_orig2 = xstrdup (optarg);
 		    parse_tagdate (&xjoin_rev2, &xjoin_date2, optarg);
 		}
-		else
+		else if (jrev_count == 1)
 		{
 		    join_orig1 = xstrdup (optarg);
 		    parse_tagdate (&xjoin_rev1, &xjoin_date1, optarg);