public void AddComment(string commentId, string commentText) { if (!HttpContext.Current.User.Identity.IsAuthenticated) return; CommentId cid = new CommentId(commentId); if (!cid.HasParsed()) return; int? result = null; CodeReviewDataContext dataContext = new CodeReviewDataContext( System.Configuration.ConfigurationManager.ConnectionStrings[Config.ConnectionString].ConnectionString); dataContext.AddComment(cid.FileVersionId, cid.Line, cid.LineStamp, commentText, ref result); dataContext.Connection.Close(); dataContext.Dispose(); }
/// <summary> /// Adds an attachment to code review. /// </summary> /// <param name="context"> Database context. </param> /// <param name="userName"> User alias. </param> /// <param name="cl"> Change list to modify. </param> /// <param name="link"> The file or web page URL. </param> /// <param name="linkDescr"> The text (optional). </param> private static void AddAttachment(CodeReviewDataContext context, string userName, string cl, string link, string linkDescr) { var changeListQuery = from ch in context.ChangeLists where ch.CL.Equals(cl) && ch.UserName.Equals(userName) && ch.Stage == 0 select ch.Id; if (changeListQuery.Count() != 1) { Console.WriteLine("No active change in database."); return; } int cid = changeListQuery.Single(); int? result = null; context.AddAttachment(cid, linkDescr, link, ref result); Console.WriteLine("Attachment submitted."); }
/// <summary> /// Tests if the current configuration works by sending email. /// </summary> /// <param name="email"> Who to send email to. </param> private static void TestSetup(string email) { Console.WriteLine("Testing notifier setup."); Console.Write("1. Attempting to load configuration... "); if (!ReviewNotifierConfiguration.Load()) { Console.WriteLine("Failure!"); Console.WriteLine("Run 'ReviewNotifier help' for help with configuring this program."); return; } Console.WriteLine("Success!"); Console.Write("2. Attempting to connect to the database... "); CodeReviewDataContext context = new CodeReviewDataContext(Config.Database); int openReviews = (from rr in context.ChangeLists where rr.Stage == 0 select rr).Count(); int totalReviews = (from rr in context.ChangeLists select rr).Count(); int files = (from ff in context.ChangeFiles select ff).Count(); int comments = (from cc in context.Comments select cc).Count(); Console.WriteLine("Success!"); string mailbody = "If you are reading this message, the notifier configuration should be correct.\n\n" + "Check that the mail has come from the right account, and that the following stats are reasonable:\n" + " Open reviews: " + openReviews + "\n Total reviews: " + totalReviews + "\n Total files in all reviews: " + files + "\n Total comments in all reviews: " + comments + "\n\nRespectfully,\n Your friendly review notifier.\n"; bool result = false; if (!string.IsNullOrEmpty(Config.EmailService)) { Console.Write("3. Sending mail using Exchange protocol... "); List<MessageType> mail = new List<MessageType>(); mail.Add(MakeExchangeMessage(email + "@" + Config.EmailDomain, Config.FromEmail == null ? null : Config.FromEmail + "@" + Config.EmailDomain, email + "@" + Config.EmailDomain, "A test email from Malevich notifier - sent via Exchange transport", mailbody, false, 0, false)); result = SendExchangeMail(Config, mail); } else if (!string.IsNullOrEmpty(Config.SmtpServer)) { Console.Write("3. Sending mail using SMTP protocol... "); List<MailMessage> mail = new List<MailMessage>(); mail.Add(MakeSmtpMessage(email + "@" + Config.EmailDomain, Config.FromEmail == null ? null : Config.FromEmail + "@" + Config.EmailDomain, ResolveUser(Config, Config.User), ResolveUser(Config, Config.User), "A test email from Malevich notifier - sent via SMTP transport", mailbody, false, 0, false)); result = SendSmtpMail(Config, mail); } else { // This should really never happen. Console.WriteLine("Failure: mail transport is not configured!"); } if (result) { Console.WriteLine("Success! (or so we think: check your inbox!"); Console.WriteLine("Email was sent to {0} @ {1}", email, Config.EmailDomain); } }
/// <summary> /// Verifies that there are no pending reviews for this change list. /// </summary> /// <param name="context"> Database context. </param> /// <param name="cid"> Change list ID. </param> /// <returns></returns> private static bool NoPendingReviews(CodeReviewDataContext context, int cid) { var unsubmittedReviewsQuery = from rr in context.Reviews where rr.ChangeListId == cid && !rr.IsSubmitted select rr; bool printedTitle = false; int unsubmittedComments = 0; foreach (Review review in unsubmittedReviewsQuery) { int comments = (from cc in context.Comments where cc.ReviewId == review.Id select cc).Count(); if (comments == 0) continue; unsubmittedComments += comments; if (!printedTitle) { printedTitle = true; Console.WriteLine("Reviews are still pending for this change list:"); } Console.WriteLine("{0} : {1} comments.", review.UserName, comments); } return unsubmittedComments == 0; }
/// <summary> /// Marks review as closed. /// </summary> /// <param name="context"> Database context. </param> /// <param name="userName"> User alias. </param> /// <param name="sourceControlId"> Source control ID. </param> /// <param name="cl"> Review number (source control side). </param> /// <param name="force"> Whether to force closing the review even if there are pending changes. </param> /// <param name="admin"> Close the review in admin mode, regardless of the user. </param> private static void MarkChangeListAsClosed(CodeReviewDataContext context, string userName, int sourceControlId, string cl, bool force, bool admin) { var cid = (from ch in context.ChangeLists where ch.SourceControlId == sourceControlId && ch.CL.Equals(cl) && ch.Stage == 0 && (admin || ch.UserName.Equals(userName)) select ch.Id).FirstOrDefault(); if (cid == null) { Console.WriteLine("No active change in database."); return; } if (force || NoPendingReviews(context, cid)) { context.SubmitChangeList(cid); Console.WriteLine("{0} closed. Use 'review reopen {0}' to reopen.", cl); } else { Console.WriteLine("Pending review detected. If you want to close this change list"); Console.WriteLine("anyway, use the --force."); } }
/// <summary> /// The main function. Parses arguments, and if there is enough information, calls ProcessCodeReview. /// </summary> /// <param name="args"> Arguments passed by the system. </param> static void Main(string[] args) { if (args.Length == 0) { DisplayHelp(); return; } // First, rummage through the environment, path, and the directories trying to detect the source // control system. string sourceControlInstance = Environment.GetEnvironmentVariable("REVIEW_INSTANCE"); SourceControlSettings sdSettings = SourceDepotInterface.GetSettings(); SourceControlSettings p4Settings = PerforceInterface.GetSettings(); SourceControlSettings tfsSettings = SourceControl.Tfs.Factory.GetSettings(); SourceControlSettings svnSettings = SubversionInterface.GetSettings(); string databaseServer = Environment.GetEnvironmentVariable("REVIEW_DATABASE"); string command = null; // Now go through the command line to get other options. string link = null; string linkDescr = null; string description = null; bool force = false; bool admin = false; string changeId = null; string newChangeId = null; bool includeBranchedFiles = false; List<string> reviewers = new List<string>(); List<string> invitees = new List<string>(); SourceControl.SourceControlType? sourceControlType = null; SourceControlSettings settings = new SourceControlSettings(); string impersonatedUserName = null; List<string> bugIds = new List<string>(); bool verbose = false; bool preview = false; for (int i = 0; i < args.Length; ++i) { if (i < args.Length - 1) { if (args[i].EqualsIgnoreCase("--sourcecontrol")) { if ("TFS".EqualsIgnoreCase(args[i + 1])) { sourceControlType = SourceControl.SourceControlType.TFS; } else if ("P4".EqualsIgnoreCase(args[i + 1])) { sourceControlType = SourceControl.SourceControlType.PERFORCE; } else if ("SD".EqualsIgnoreCase(args[i + 1])) { sourceControlType = SourceControl.SourceControlType.SD; } else if ("SVN".EqualsIgnoreCase(args[i + 1])) { sourceControlType = SourceControl.SourceControlType.SUBVERSION; } else { Console.WriteLine("error : source control '{0}' is not supported.", args[i + 1]); return; } ++i; continue; } if (args[i].EqualsIgnoreCase("--port")) { settings.Port = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--server")) { settings.Port = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--client")) { settings.Client = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--workspace")) { settings.Client = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--tfsdiffsource")) { string diff = args[i + 1]; if (diff.EqualsIgnoreCase("local")) { settings.Diff = SourceControlSettings.DiffSource.Local; } else if (diff.EqualsIgnoreCase("shelf")) { settings.Diff = SourceControlSettings.DiffSource.Server; } else { Console.WriteLine( "error : unrecognized value of TFS diff source. Should be either shelf or local."); return; } ++i; continue; } if (args[i].EqualsIgnoreCase("--user")) { settings.User = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--password")) { settings.Password = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--clientexe")) { settings.ClientExe = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--database")) { databaseServer = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--instance")) { sourceControlInstance = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--invite")) { if (!AddReviewers(invitees, args[i + 1])) return; ++i; continue; } if (args[i].EqualsIgnoreCase("--link")) { link = args[i + 1]; ++i; if (!(link.StartsWith(@"file://\\") || link.StartsWith("http://") || link.StartsWith("https://"))) { Console.WriteLine("error : incorrect link specification : should start with http://, https://, or " + "file://. If the latter is used, the supplied file name should be UNC."); return; } continue; } if (args[i].EqualsIgnoreCase("--linkdescr")) { linkDescr = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--description")) { description = args[i + 1]; ++i; continue; } if (args[i].EqualsIgnoreCase("--impersonate")) { impersonatedUserName = args[++i]; continue; } if (args[i].EqualsIgnoreCase("--bugid")) { bugIds.Add(args[++i]); continue; } } if (args[i].EqualsIgnoreCase("--force")) { force = true; continue; } if (args[i].EqualsIgnoreCase("--admin")) { admin = true; continue; } if (args[i].EqualsIgnoreCase("--includebranchedfiles")) { includeBranchedFiles = true; continue; } if (args[i].EqualsIgnoreCase("--verbose")) { verbose = true; continue; } if (args[i].EqualsIgnoreCase("--preview")) { preview = true; continue; } if (args[i].EqualsIgnoreCase("help") || args[i].EqualsIgnoreCase("/help") || args[i].EqualsIgnoreCase("--help") || args[i].EqualsIgnoreCase("-help") || args[i].EqualsIgnoreCase("/h") || args[i].EqualsIgnoreCase("-h") || args[i].EqualsIgnoreCase("-?") || args[i].EqualsIgnoreCase("/?")) { DisplayHelp(); return; } if (args[i].StartsWith("-")) { Console.WriteLine("error : unrecognized flag: {0}", args[i]); return; } if (command == null && args[i].EqualsIgnoreCase("review") || args[i].EqualsIgnoreCase("close") || args[i].EqualsIgnoreCase("delete") || args[i].EqualsIgnoreCase("reopen") || args[i].EqualsIgnoreCase("rename") || args[i].EqualsIgnoreCase("addlink")) { command = args[i]; continue; } if (changeId == null) { changeId = args[i]; continue; } if ("addlink".EqualsIgnoreCase(command)) { if (link == null) { link = args[i]; continue; } else if (linkDescr == null) { linkDescr = args[i]; continue; } } if ("rename".EqualsIgnoreCase(command)) { if (newChangeId == null) { newChangeId = args[i]; continue; } } if (command == null || "review".EqualsIgnoreCase(command)) { if (!AddReviewers(reviewers, args[i])) return; continue; } Console.WriteLine("error : {0} is not recognized. --help for help", args[i]); return; } string userName = impersonatedUserName ?? Environment.GetEnvironmentVariable("USERNAME"); if (changeId == null) { Console.WriteLine("error : change list is required. Type 'review help' for help."); return; } if (databaseServer == null) { Console.WriteLine("error : database server is required. Type 'review help' for help."); return; } if (link == null && linkDescr != null) { Console.WriteLine("error : if you supply link description, the link must also be present."); return; } if (impersonatedUserName != null && !admin) { Console.WriteLine("error : --impersonate may only be used in conjunction with --admin."); return; } int sourceControlInstanceId; if (!Int32.TryParse(sourceControlInstance, out sourceControlInstanceId)) sourceControlInstanceId = DefaultSourceControlInstanceId; // These commands do not require source control - get them out the way first. if (command != null) { CodeReviewDataContext context = new CodeReviewDataContext("Data Source=" + databaseServer + ";Initial Catalog=CodeReview;Integrated Security=True"); if (command.EqualsIgnoreCase("close")) { MarkChangeListAsClosed(context, userName, sourceControlInstanceId, changeId, force, admin); return; } else if (command.EqualsIgnoreCase("delete")) { DeleteChangeList(context, userName, sourceControlInstanceId, changeId, force, admin); return; } else if (command.EqualsIgnoreCase("rename")) { RenameChangeList(context, userName, sourceControlInstanceId, changeId, newChangeId, admin); return; } else if (command.EqualsIgnoreCase("reopen")) { ReopenChangeList(context, userName, sourceControlInstanceId, changeId, admin); return; } else if (command.EqualsIgnoreCase("addlink")) { if (link != null) AddAttachment(context, userName, changeId, link, linkDescr); else Console.WriteLine("You need to supply the link to add."); return; } } // If we have the client, maybe we can guess the source control... if (sourceControlType == null && settings.ClientExe != null) { string clientExeFile = Path.GetFileName(settings.ClientExe); if ("sd.exe".EqualsIgnoreCase(clientExeFile)) sourceControlType = SourceControl.SourceControlType.SD; else if ("p4.exe".EqualsIgnoreCase(clientExeFile)) sourceControlType = SourceControl.SourceControlType.PERFORCE; else if ("tf.exe".EndsWith(clientExeFile, StringComparison.InvariantCultureIgnoreCase)) sourceControlType = SourceControl.SourceControlType.TFS; else if ("svn.exe".EqualsIgnoreCase(clientExeFile)) sourceControlType = SourceControl.SourceControlType.SUBVERSION; } // Attempt to detect the source control system. if (sourceControlType == null) { if (sdSettings.Port != null && sdSettings.Client != null && sdSettings.ClientExe != null) sourceControlType = SourceControl.SourceControlType.SD; if (p4Settings.Port != null && p4Settings.Client != null && p4Settings.ClientExe != null) sourceControlType = SourceControl.SourceControlType.PERFORCE; if (tfsSettings.Port != null) sourceControlType = SourceControl.SourceControlType.TFS; if (svnSettings.Port != null && svnSettings.Client != null && svnSettings.ClientExe != null) sourceControlType = SourceControl.SourceControlType.SUBVERSION; if (sourceControlType == null) { Console.WriteLine("Could not determine the source control system."); Console.WriteLine("User 'review help' for help with specifying it."); return; } } // If source control is explicitly specified... if (sourceControlType == SourceControl.SourceControlType.TFS) { if (settings.Client != null) tfsSettings.Client = settings.Client; if (settings.Port != null) tfsSettings.Port = settings.Port; if (settings.User != null) tfsSettings.User = settings.User; if (settings.Password != null) tfsSettings.Password = settings.Password; if (settings.ClientExe != null) tfsSettings.ClientExe = settings.ClientExe; if (settings.Diff != SourceControlSettings.DiffSource.Unspecified) tfsSettings.Diff = settings.Diff; if (tfsSettings.Port == null) { Console.WriteLine("Could not determine tfs server. Consider specifying it on the command line or " + "in the environment."); return; } if (tfsSettings.Client == null && tfsSettings.Diff == SourceControlSettings.DiffSource.Local) { Console.WriteLine("Could not determine tfs workspace. Consider specifying it on the command line " + "or in the environment."); return; } } if (sourceControlType == SourceControl.SourceControlType.PERFORCE) { if (settings.Client != null) p4Settings.Client = settings.Client; if (settings.Port != null) p4Settings.Port = settings.Port; if (settings.User != null) p4Settings.User = settings.User; if (settings.Password != null) p4Settings.Password = settings.Password; if (settings.ClientExe != null) p4Settings.ClientExe = settings.ClientExe; if (p4Settings.ClientExe == null) { Console.WriteLine( "Could not find p4.exe. Consider putting it in the path, or on the command line."); return; } if (p4Settings.Port == null) { Console.WriteLine("Could not determine the server port. " + "Consider putting it on the command line, or in p4 config file."); return; } if (p4Settings.Client == null) { Console.WriteLine("Could not determine the perforce client. " + "Consider putting it on the command line, or in p4 config file."); return; } } if (sourceControlType == SourceControl.SourceControlType.SUBVERSION) { if (settings.Client != null) svnSettings.Client = settings.Client; if (settings.Port != null) svnSettings.Port = settings.Port; if (settings.User != null) svnSettings.User = settings.User; if (settings.Password != null) svnSettings.Password = settings.Password; if (settings.ClientExe != null) svnSettings.ClientExe = settings.ClientExe; if (svnSettings.ClientExe == null) { Console.WriteLine( "Could not find svn.exe. Consider putting it in the path, or on the command line."); return; } if (svnSettings.Port == null) { Console.WriteLine("Could not determine the server Url. " + "Consider putting it on the command line."); return; } } if (sourceControlType == SourceControl.SourceControlType.SD) { if (settings.Client != null) sdSettings.Client = settings.Client; if (settings.Port != null) sdSettings.Port = settings.Port; if (settings.ClientExe != null) sdSettings.ClientExe = settings.ClientExe; if (sdSettings.ClientExe == null) { Console.WriteLine( "Could not find sd.exe. Consider putting it in the path, or on the command line."); return; } if (sdSettings.Port == null) { Console.WriteLine("Could not determine the server port. " + "Consider putting it on the command line, or in sd.ini."); return; } if (sdSettings.Client == null) { Console.WriteLine("Could not determine the source depot client. " + "Consider putting it on the command line, or in sd.ini."); return; } } try { ISourceControl sourceControl; IBugServer bugTracker = null; if (sourceControlType == SourceControl.SourceControlType.SD) sourceControl = SourceDepotInterface.GetInstance(sdSettings.ClientExe, sdSettings.Port, sdSettings.Client, sdSettings.Proxy); else if (sourceControlType == SourceControl.SourceControlType.PERFORCE) sourceControl = PerforceInterface.GetInstance(p4Settings.ClientExe, p4Settings.Port, p4Settings.Client, p4Settings.User, p4Settings.Password); else if (sourceControlType == SourceControl.SourceControlType.SUBVERSION) sourceControl = SubversionInterface.GetInstance(svnSettings.ClientExe, svnSettings.Port, svnSettings.Client); else if (sourceControlType == SourceControl.SourceControlType.TFS) { sourceControl = SourceControl.Tfs.Factory.GetISourceControl( tfsSettings.Port, tfsSettings.Client, tfsSettings.ClientOwner, tfsSettings.User, tfsSettings.Password, tfsSettings.Diff == SourceControlSettings.DiffSource.Server); bugTracker = SourceControl.Tfs.Factory.GetIBugServer( tfsSettings.Port, tfsSettings.Client, tfsSettings.ClientOwner, tfsSettings.User, tfsSettings.Password); } else throw new ApplicationException("Unknown source control system."); if (verbose) { ILogControl logControl = sourceControl as ILogControl; if (logControl != null) logControl.SetLogLevel(LogOptions.ClientUtility); else Console.WriteLine("Note: client log requested, but not supported by the utility."); } if (!sourceControl.Connect()) { Console.WriteLine("Failed to connect to the source control system."); return; } ProcessCodeReview(databaseServer, sourceControl, sourceControlInstanceId, changeId, reviewers, invitees, link, linkDescr, description, bugTracker, bugIds, force, includeBranchedFiles, preview, impersonatedUserName); sourceControl.Disconnect(); } catch (SourceControlRuntimeError) { // The error condition has already been printed out at the site where this has been thrown. Console.WriteLine("Code review has not been submitted!"); } catch (SqlException ex) { Console.WriteLine("Could not connect to Malevich database. This could be a temporary"); Console.WriteLine("network problem or a misconfigured database name. Please ensure that"); Console.WriteLine("the database ({0}) is specified correctly.", databaseServer); Console.WriteLine("If this is a new Malevich server installation, please ensure that"); Console.WriteLine("SQL Server TCP/IP protocol is enabled, and its ports are open in"); Console.WriteLine("the firewall."); if (verbose) { Console.WriteLine("Exception information (please include in bug reports):"); Console.WriteLine("{0}", ex); } else { Console.WriteLine("Use --verbose flag to show detailed error information."); } } }
/// <summary> /// Marks review as deleted. /// </summary> /// <param name="context"> Database context. </param> /// <param name="userName"> User alias. </param> /// <param name="sourceControlId"> Source control ID. </param> /// <param name="cl"> Review number (source control side). </param> /// <param name="force"> Whether to force closing the review even if there are pending changes. </param> /// <param name="admin"> Close the review in admin mode, regardless of the user. </param> private static void DeleteChangeList(CodeReviewDataContext context, string userName, int sourceControlId, string cl, bool force, bool admin) { int[] cids = admin ? (from ch in context.ChangeLists where ch.SourceControlId == sourceControlId && ch.CL.Equals(cl) && ch.Stage == 0 select ch.Id).ToArray() : (from ch in context.ChangeLists where ch.SourceControlId == sourceControlId && ch.CL.Equals(cl) && ch.UserName.Equals(userName) && ch.Stage == 0 select ch.Id).ToArray(); if (cids.Length != 1) { Console.WriteLine("No active change in database."); return; } if (force || NoPendingReviews(context, cids[0])) { context.DeleteChangeList(cids[0]); Console.WriteLine("{0} deleted. Use 'review reopen {0}' to undelete.", cl); } else { Console.WriteLine("Pending review detected. If you want to delete this change list"); Console.WriteLine("anyway, use the --force."); } }
public int GetNumberOfReviewsWhereIAmTheReviewee() { string alias = GetUserAlias(); if (alias == null) return 0; CodeReviewDataContext dataContext = new CodeReviewDataContext( System.Configuration.ConfigurationManager.ConnectionStrings[Config.ConnectionString].ConnectionString); int result = (from cc in dataContext.ChangeLists where cc.UserName == alias && cc.Stage == 0 select cc).Distinct().Count(); dataContext.Connection.Close(); dataContext.Dispose(); return result; }
/// <summary> /// Main driver for the code review submission tool. /// </summary> /// <param name="context"> The database context. </param> /// <param name="sd"> Source control client. </param> /// <param name="sourceControlInstanceId"> The ID of source control instance to use. /// This is an ID of a record in the database that is unique for a given CL namespace.</param> /// <param name="changeList"> CL. </param> /// <param name="reviewers"> The list of people to who to send the code review request. </param> /// <param name="invitees"> The list of people who are invited to participate in the code review /// (but need to positively acknowledge the invitation by choosing to review the code). </param> /// <param name="link"> Optionally, a link to a file or a web page to be displayed in CL page. </param> /// <param name="linkDescr"> An optional description of the said link. </param> /// <param name="description"> An optional description of the changelist, overrides any description /// from the source control tool. </param> /// <param name="bugTracker">The server for accessing bugs.</param> /// <param name="bugIds">List of bugs to associate with review page.</param> /// <param name="force"> If branched files are included, confirms the submission even if there /// are too many files. </param> /// <param name="includeBranchedFiles"> </param> /// <param name="preview">If true, do not commit changes.</param> private static void ProcessCodeReview( string databaseServer, ISourceControl sd, int sourceControlInstanceId, string changeList, List<string> reviewers, List<string> invitees, string link, string linkDescr, string description, IBugServer bugServer, List<string> bugIds, bool force, bool includeBranchedFiles, bool preview, string impersonateUserName) { Change change = sd.GetChange(changeList, includeBranchedFiles); if (change == null) return; changeList = change.ChangeListFriendlyName ?? changeList; if (change == null) return; if (includeBranchedFiles && !force) { int branchedFiles = 0; foreach (SourceControl.ChangeFile file in change.Files) { if (file.IsText && (file.Action == SourceControl.ChangeFile.SourceControlAction.BRANCH || file.Action == SourceControl.ChangeFile.SourceControlAction.INTEGRATE)) ++branchedFiles; } if (branchedFiles > MaximumIntegratedFiles) { Console.WriteLine("There are {0} branched/integrated files in this change.", branchedFiles); Console.WriteLine("Including the full text of so many files in review may increase the size"); Console.WriteLine("of the review database considerably."); Console.Write("Are you sure you want to proceed (Yes/No)? "); string response = Console.ReadLine(); Console.WriteLine("NOTE: In the future you can override this check by specifying --force"); Console.WriteLine("on the command line."); if (response[0] != 'y' && response[0] != 'Y') return; } } if (!VerifyDiffIntegrity(change)) return; CodeReviewDataContext context = new CodeReviewDataContext("Data Source=" + databaseServer + ";Initial Catalog=CodeReview;Integrated Security=True"); var existingReviewQuery = from rv in context.ChangeLists where rv.CL == changeList && rv.SourceControlId == sourceControlInstanceId select rv; // is this a new review, or a refresh of an existing one? bool isNewReview = (existingReviewQuery.Count() == 0); int? changeId = null; if (description == null) description = change.Description; context.Connection.Open(); using (context.Connection) using (context.Transaction = context.Connection.BeginTransaction(System.Data.IsolationLevel.Snapshot)) { // This more like "GetOrAddChangeList", as it returns the id of any pre-existing changelist // matching 'changeList'. if (impersonateUserName == null) { context.AddChangeList( sourceControlInstanceId, change.SdClientName, changeList, description, change.TimeStamp.ToUniversalTime(), ref changeId); } else { var changeListDb = (from c in context.ChangeLists where c.SourceControlId == sourceControlInstanceId && c.UserName == impersonateUserName && c.UserClient == change.SdClientName && c.CL == changeList select c).FirstOrDefault(); if (changeListDb == null) { changeListDb = new ChangeList() { SourceControlId = sourceControlInstanceId, UserName = impersonateUserName, UserClient = change.SdClientName, CL = changeList, Description = description, TimeStamp = change.TimeStamp.ToUniversalTime(), Stage = 0 }; context.ChangeLists.InsertOnSubmit(changeListDb); context.SubmitChanges(); // Not actually submitted until transaction completes. } changeId = changeListDb.Id; } // Get the list of files corresponding to this changelist already on the server. var dbChangeFiles = (from fl in context.ChangeFiles where fl.ChangeListId == changeId && fl.IsActive select fl) .OrderBy(file => file.ServerFileName) .GetEnumerator(); var inChangeFiles = (from fl in change.Files select fl) .OrderBy(file => file.ServerFileName) .GetEnumerator(); bool dbChangeFilesValid = dbChangeFiles.MoveNext(); bool inChangeFilesValid = inChangeFiles.MoveNext(); // Uses bitwise OR to ensure that both MoveNext methods are invoked. FileExistsIn existsIn = FileExistsIn.Neither; while (dbChangeFilesValid || inChangeFilesValid) { int comp; if (!dbChangeFilesValid) // No more files in database comp = 1; else if (!inChangeFilesValid) // No more files in change list. comp = -1; else comp = string.Compare(dbChangeFiles.Current.ServerFileName, inChangeFiles.Current.ServerFileName); if (comp < 0) // We have a file in DB, but not in source control. Delete it from DB. { Console.WriteLine("File {0} has been dropped from the change list.", dbChangeFiles.Current.ServerFileName); context.RemoveFile(dbChangeFiles.Current.Id); dbChangeFilesValid = dbChangeFiles.MoveNext(); existsIn = FileExistsIn.Database; continue; } SourceControl.ChangeFile file = inChangeFiles.Current; int? fid = null; if (comp > 0) // File in source control, but not in DB { Console.WriteLine("Adding file {0}", file.ServerFileName); context.AddFile(changeId, file.LocalFileName, file.ServerFileName, ref fid); existsIn = FileExistsIn.Change; } else // Both files are here. Need to check the versions. { fid = dbChangeFiles.Current.Id; existsIn = FileExistsIn.Both; } bool haveBase = (from bv in context.FileVersions where bv.FileId == fid && bv.Revision == file.Revision && bv.IsRevisionBase select bv).Count() > 0; var versionQuery = from fv in context.FileVersions where fv.FileId == fid && fv.Revision == file.Revision orderby fv.Id descending select fv; var version = versionQuery.FirstOrDefault(); bool haveVersion = false; if (version != null && version.Action == (int)file.Action && BodiesEqual(NormalizeLineEndings(file.Data), NormalizeLineEndings(version.Text))) haveVersion = true; int? vid = null; if (!haveBase && file.IsText && (file.Action == SourceControl.ChangeFile.SourceControlAction.EDIT || (file.Action == SourceControl.ChangeFile.SourceControlAction.INTEGRATE && includeBranchedFiles))) { string fileBody; DateTime? dateTime; fileBody = sd.GetFile( file.OriginalServerFileName == null ? file.ServerFileName : file.OriginalServerFileName, file.Revision, out dateTime); if (fileBody == null) { Console.WriteLine("ERROR: Could not retrieve {0}#{1}", file.ServerFileName, file.Revision); return; } Console.WriteLine("Adding base revision for {0}#{1}", file.ServerFileName, file.Revision); context.AddVersion(fid, file.Revision, (int)file.Action, dateTime, true, true, true, fileBody, ref vid); } else { // Do this so we print the right thing. haveBase = true; } if (!haveVersion) { if (file.Action == SourceControl.ChangeFile.SourceControlAction.DELETE) { context.AddVersion( fid, file.Revision, (int)file.Action, null, file.IsText, false, false, null, ref vid); } else if ((file.Action == SourceControl.ChangeFile.SourceControlAction.RENAME) || !file.IsText) { context.AddVersion(fid, file.Revision, (int)file.Action, file.LastModifiedTime, file.IsText, false, false, null, ref vid); } else if (file.Action == SourceControl.ChangeFile.SourceControlAction.ADD || file.Action == SourceControl.ChangeFile.SourceControlAction.BRANCH) { context.AddVersion(fid, file.Revision, (int)file.Action, file.LastModifiedTime, file.IsText, true, false, file.Data, ref vid); } else if (file.Action == SourceControl.ChangeFile.SourceControlAction.EDIT || file.Action == SourceControl.ChangeFile.SourceControlAction.INTEGRATE) { context.AddVersion(fid, file.Revision, (int)file.Action, file.LastModifiedTime, file.IsText, false, false, file.Data, ref vid); } string textFlag = file.IsText ? "text" : "binary"; string action; switch (file.Action) { case SourceControl.ChangeFile.SourceControlAction.ADD: action = "add"; break; case SourceControl.ChangeFile.SourceControlAction.EDIT: action = "edit"; break; case SourceControl.ChangeFile.SourceControlAction.DELETE: action = "delete"; break; case SourceControl.ChangeFile.SourceControlAction.BRANCH: action = "branch"; break; case SourceControl.ChangeFile.SourceControlAction.INTEGRATE: action = "integrate"; break; case SourceControl.ChangeFile.SourceControlAction.RENAME: action = "rename"; break; default: action = "unknown"; break; } if (version != null && vid == version.Id) { // The file was already there. This happens sometimes because SQL rountrip (to database // and back) is not an identity: somtimes the non-graphical characters change depending // on the database code page. But if the database has returned a number, and this number // is the same as the previous version id, we know that the file has not really been added. haveVersion = true; } else { Console.WriteLine("Added version for {0}#{1}({2}, {3})", file.ServerFileName, file.Revision, textFlag, action); } } if (haveBase && haveVersion) Console.WriteLine("{0} already exists in the database.", file.ServerFileName); if ((existsIn & FileExistsIn.Database) == FileExistsIn.Database) dbChangeFilesValid = dbChangeFiles.MoveNext(); if ((existsIn & FileExistsIn.Change) == FileExistsIn.Change) inChangeFilesValid = inChangeFiles.MoveNext(); existsIn = FileExistsIn.Neither; } foreach (string reviewer in reviewers) { int? reviewId = null; context.AddReviewer(reviewer, changeId.Value, ref reviewId); } foreach (string invitee in invitees) { context.AddReviewRequest(changeId.Value, invitee); } if (link != null) { int? attachmentId = null; context.AddAttachment(changeId.Value, linkDescr, link, ref attachmentId); } if (preview) context.Transaction.Rollback(); else context.Transaction.Commit(); } var reviewSiteUrl = Environment.GetEnvironmentVariable("REVIEW_SITE_URL"); if (reviewSiteUrl != null) { var reviewPage = reviewSiteUrl; if (!reviewPage.EndsWith("/")) reviewPage += "/"; reviewPage += @"default.aspx?cid=" + changeId.ToString(); Console.WriteLine("Change {0} is ready for review, and may be viewed at", changeList); Console.WriteLine(" {0}", reviewPage); var allBugIds = Enumerable.Union(change.BugIds, bugIds); if (bugServer != null && allBugIds.Count() > 0) { Console.WriteLine("Connecting to TFS Work Item Server"); if (bugServer.Connect()) { foreach (var bugId in allBugIds) { var bug = bugServer.GetBug(bugId); if (bug.AddLink(new Uri(reviewPage), null)) Console.WriteLine("Bug {0} has been linked to review page.", bugId); } } } } else { Console.WriteLine("Change {0} is ready for review.", changeList); if (isNewReview) { if (reviewers.Count == 0 && invitees.Count == 0) { Console.WriteLine("Note: no reviewers specified. You can add them later using this utility."); } else { Console.WriteLine("If the mail notifier is enabled, the reviewers will shortly receive mail"); Console.WriteLine("asking them to review your changes."); } } else { Console.WriteLine("Note: existing reviewers will not be immediately informed of this update."); Console.WriteLine("To ask them to re-review your updated changes, you can visit the review website"); Console.WriteLine("and submit a response."); } } if (preview) Console.WriteLine("In preview mode -- no actual changes committed."); }
/// <summary> /// Gets the data context out of the system cache, or the database. /// </summary> /// <param name="user"> Authenticated user alias (no DOMAIN). </param> /// <param name="cache"> System cache. </param> /// <param name="context"> Data connection to the database. </param> /// <returns> User context. </returns> public static UserContext GetUserContext(string user, Cache cache, CodeReviewDataContext context) { Hashtable allUserContexts = (Hashtable)cache[UserContextTableName]; if (allUserContexts == null) cache[UserContextTableName] = allUserContexts = new Hashtable(); UserContext uc = (UserContext)allUserContexts[user]; if (uc == null) { uc = new UserContext(user); var dataContexts = from cc in context.UserContexts where cc.UserName == user select cc; foreach (DataModel.UserContext dataContext in dataContexts) { if (dataContext.KeyName.Equals(UserContext.TEXT_SIZE, StringComparison.OrdinalIgnoreCase)) { int value; if (int.TryParse(dataContext.Value, out value)) uc.TextSize = value; } else if (dataContext.KeyName.Equals(UserContext.TEXT_FONT, StringComparison.OrdinalIgnoreCase)) { uc.TextFont = dataContext.Value; } else if (dataContext.KeyName.Equals(UserContext.HINT_MASK, StringComparison.OrdinalIgnoreCase)) { int value; if (int.TryParse(dataContext.Value, out value)) uc.HintsMask = value; } else if (dataContext.KeyName.Equals(UserContext.MAX_LINE_LENGTH, StringComparison.OrdinalIgnoreCase)) { int value; if (int.TryParse(dataContext.Value, out value)) uc.MaxLineLength = value; } else if (dataContext.KeyName.Equals(UserContext.SPACES_PER_TAB, StringComparison.OrdinalIgnoreCase)) { int value; if (int.TryParse(dataContext.Value, out value)) uc.SpacesPerTab = value; } else if (dataContext.KeyName.Equals(UserContext.UNIFIED_DIFF_VIEW, StringComparison.OrdinalIgnoreCase)) { bool value; if (bool.TryParse(dataContext.Value, out value)) uc.UnifiedDiffView = value; } else if (dataContext.KeyName.Equals(UserContext.COMMENT_CLICK_MODE, StringComparison.OrdinalIgnoreCase)) { try { uc.CommentClickMode = (CommentClickMode)Enum.Parse( typeof(CommentClickMode), dataContext.Value); } catch (ArgumentNullException) { } catch (ArgumentException) { } } // Compat with previous "double/single" click database entries else if (dataContext.KeyName.Equals("commentstyle", StringComparison.OrdinalIgnoreCase)) { int value; if (int.TryParse(dataContext.Value, out value)) { uc.CommentClickMode = (value == 0 ? CommentClickMode.SingleClickAnywhere : CommentClickMode.DoubleClickAnywhere); // Save translated setting context.SetUserContext(UserContext.COMMENT_CLICK_MODE, uc.CommentClickMode.ToString()); } // Remove old setting from database context.UserContexts.DeleteOnSubmit(dataContext); context.SubmitChanges(); } else if (dataContext.KeyName.Equals( UserContext.AUTO_COLLAPSE_COMMENTS, StringComparison.OrdinalIgnoreCase)) { bool value; if (bool.TryParse(dataContext.Value, out value)) uc.AutoCollapseComments = value; } } allUserContexts[user] = uc; } return uc; }
/// <summary> /// Sets the source control to point to the selected web site. /// </summary> /// <param name="installParams"> Installation parameters. </param> private static void ConfigureSourceControlInDatabase(InstallParameters installParams) { try { using (CodeReviewDataContext context = new CodeReviewDataContext("Data Source=" + installParams.Database + ";Initial Catalog=CodeReview;Integrated Security=True")) { SourceControl[] sourceControls = (from sc in context.SourceControls select sc).ToArray(); if (sourceControls.Length == 1) { sourceControls[0].WebsiteName = "/" + (installParams.WebApplicationName == null ? "Malevich" : installParams.WebApplicationName); context.SubmitChanges(); } } } catch (Exception ex) { Log(ex.ToString()); throw new InstallationException("Failed to adjust source control."); } }
/// <summary> /// Installs or upgrades the database. /// </summary> /// <param name="installParams"> Installation parameters. </param> private static void InstallDatabase(InstallParameters installParams) { Log("Installing the database."); int changeListCount = 0; if (installParams.DatabaseDirectory == null) { CreateDatabase(installParams); } else { BackupDatabase(installParams); if (installParams.FixChangeListTimeStamps) FixChangeListTimeStamps(installParams); SqlConnection sqlConnection = new SqlConnection("Data Source=" + installParams.Database + ";Initial Catalog=CodeReview;Integrated Security=True"); sqlConnection.Open(); using (SqlCommand command = new SqlCommand("SELECT COUNT(Id) FROM ChangeList", sqlConnection)) changeListCount = (int)command.ExecuteScalar(); sqlConnection.Close(); } Log(" Deploying the metadata:"); // Bug in vsdbcmd: if HKEY_CURRENT_USER\Software\Microsoft\VisualStudio\9.0 does not exist, the tool // does not work. Registry.CurrentUser.CreateSubKey(@"Software\Microsoft\VisualStudio\9.0").Close(); string deployer = Path.Combine(installParams.InstallSource, @"redistr\deploy\vsdbcmd.exe"); string metadata = Path.Combine(installParams.InstallSource, @"$(TargetFileName)"); string postscript = Path.Combine(installParams.InstallSource, @"Script.PostDeployment.sql"); Process client = new Process(); client.StartInfo.UseShellExecute = false; client.StartInfo.RedirectStandardError = true; client.StartInfo.RedirectStandardOutput = true; client.StartInfo.CreateNoWindow = true; client.StartInfo.FileName = deployer; client.StartInfo.Arguments = "/a:Deploy /cs:\"Data Source=" + installParams.Database + ";Integrated Security=true\" /model:\"" + metadata + "\" /dd:+ /p:TargetDatabase=CodeReview"; client.Start(); string stderr; string stdout = Malevich.Util.CommonUtils.ReadProcessOutput(client, false, out stderr); client.Dispose(); if (!String.IsNullOrEmpty(stderr)) { Log("Deployment has returned an error status:"); Log(stderr); Log("The rest of the output:"); Log(stdout); throw new InstallationException("Failed to deploy the metadata."); } Log(stdout); Log(" Running the post-deployment script."); string postdeploycommands = null; using (StreamReader pds = new StreamReader(postscript)) postdeploycommands = pds.ReadToEnd(); if (String.IsNullOrEmpty(postdeploycommands)) throw new InstallationException("Could not get postdeployment script."); try { SqlConnection sqlConnection = new SqlConnection("Data Source=" + installParams.Database + ";Initial Catalog=CodeReview;Integrated Security=True"); sqlConnection.Open(); using (SqlCommand command = new SqlCommand(postdeploycommands, sqlConnection)) command.ExecuteNonQuery(); sqlConnection.Close(); } catch (SqlException sex) { Log(sex.ToString()); throw new InstallationException("Could not deploy the database."); } Log(" Verifying the database."); try { using (CodeReviewDataContext context = new CodeReviewDataContext("Data Source=" + installParams.Database + ";Initial Catalog=CodeReview;Integrated Security=True")) { int? cid = null; context.AddChangeList(1, "Test client", "Test change list", "Test description", DateTime.Now, ref cid); ChangeList[] cl = (from cc in context.ChangeLists where cc.Id == cid.Value select cc).ToArray(); if (cl.Length != 1) throw new InstallationException( "Failed to create or query a change list in the verification step."); context.ChangeLists.DeleteOnSubmit(cl[0]); context.SubmitChanges(); if ((from cc in context.ChangeLists select cc).Count() != changeListCount) throw new InstallationException( "Verification failed - upgrade changed number of change lists in the database."); } } catch (Exception ex) { Log(ex.ToString()); throw new InstallationException("Failed to verify the database."); } }
public void DeleteComment(string commentId) { if (!HttpContext.Current.User.Identity.IsAuthenticated) return; CommentId cid = new CommentId(commentId); CodeReviewDataContext dataContext = new CodeReviewDataContext( System.Configuration.ConfigurationManager.ConnectionStrings[Config.ConnectionString].ConnectionString); var commentQuery = from cm in dataContext.Comments where cm.FileVersionId == cid.FileVersionId && cm.Line == cid.Line && cm.LineStamp == cid.LineStamp select cm.Id; if (commentQuery.Count() == 1) { int id = commentQuery.Single(); dataContext.DeleteComment(id); } dataContext.Connection.Close(); dataContext.Dispose(); }
public void RecordHintShowing(int hintNumber) { string alias = GetUserAlias(); if (alias == null) return; CodeReviewDataContext context = new CodeReviewDataContext( System.Configuration.ConfigurationManager.ConnectionStrings[Config.ConnectionString].ConnectionString); UserContext uc = UserContext.GetUserContext(alias, Context.Cache, context); long mask = 1 << (hintNumber - 1); uc.HintsMask = (uc.HintsMask == null ? 0 : uc.HintsMask.Value) | mask; context.SetUserContext(UserContext.HINT_MASK, uc.HintsMask.Value.ToString()); context.Connection.Close(); context.Dispose(); }
/// <summary> /// Lists the review comments in the string builder. /// </summary> /// <param name="context"> The data context. </param> /// <param name="reviewId"> The review object. </param> /// <param name="isBodyHtml"> If true, generate HTML comments. </param> /// <param name="malevichPath"> The URL of Malevich server. </param> private static void ListCommentsForReview(CodeReviewDataContext context, Review rw, StringBuilder sb, bool isBodyHtml, string malevichUrl) { if (rw.CommentText != null) { if (isBodyHtml) { sb.Append("<div class=\"CssMalevichOverallComment\"><pre>"); sb.Append(HttpUtility.HtmlEncode(rw.CommentText)); sb.Append("</pre></div>"); } else { sb.Append(rw.CommentText); sb.Append("\n\n\n"); } } var commentsQuery = from cm in context.Comments where cm.ReviewId == rw.Id join fv in context.FileVersions on cm.FileVersionId equals fv.Id orderby fv.FileId, cm.FileVersionId, cm.Line, cm.LineStamp select cm; Comment[] comments = commentsQuery.ToArray(); if (comments.Length == 0) return; if (isBodyHtml) sb.Append("<table class=\"CssMalevichCommentTable\">"); int currentFileVersionId = 0; int currentLine = 0; foreach (Comment comment in comments) { if (currentFileVersionId != comment.FileVersionId) { FileVersion version = comment.FileVersion; string name = version.ChangeFile.ServerFileName; int lastForwardSlash = name.LastIndexOf('/'); if (lastForwardSlash >= 0) name = name.Substring(lastForwardSlash + 1); FileVersion ver = comment.FileVersion; if (isBodyHtml) sb.Append("<tr class=\"CssMalevichEmailCommentTableRow\">" + "<td colspan=\"2\" class=\"CssMalevichEmailFileColumn\">" + "<a href=\"" + malevichUrl + "?fid=" + ver.FileId + "&vid1=" + ver.Id + "&vid2=" + ver.Id + "\" class=\"CssMalevichFileLink\">File " + FileDisplayName(name, ver) + "</a>:</td></tr>"); else sb.Append("\n\nFile " + FileDisplayName(name, ver) + ":\n"); currentFileVersionId = version.Id; currentLine = 0; } if (isBodyHtml) sb.Append( "<tr class=\"CssMalevichEmailCommentTableRow\"><td class=\"CssMalevichEmailLineColumn\">"); if (currentLine != comment.Line) { currentLine = comment.Line; sb.Append("Line " + currentLine + ":\n"); } if (isBodyHtml) { sb.Append("</td><td class=\"CssMalevichEmailCommentColumn\"><pre>"); sb.Append(HttpUtility.HtmlEncode(comment.CommentText)); sb.Append("</pre></td></tr>"); } else { sb.Append(comment.CommentText); sb.Append("\n"); } } if (isBodyHtml) sb.Append("</table>"); }
/// <summary> /// Reopens a change list. /// </summary> /// <param name="context"> Database context. </param> /// <param name="userName"> User alias. </param> /// <param name="sourceControlId"> Source control ID. </param> /// <param name="cl"> Review number (source control side). </param> /// <param name="admin"> Close the review in admin mode, regardless of the user. </param> private static void ReopenChangeList(CodeReviewDataContext context, string userName, int sourceControlId, string cl, bool admin) { int[] cids = admin ? (from ch in context.ChangeLists where ch.SourceControlId == sourceControlId && ch.CL.Equals(cl) && ch.Stage != 0 select ch.Id).ToArray() : (from ch in context.ChangeLists where ch.SourceControlId == sourceControlId && ch.CL.Equals(cl) && ch.UserName.Equals(userName) && ch.Stage != 0 select ch.Id).ToArray(); if (cids.Length != 1) { Console.WriteLine("No inactive change in database."); return; } context.ReopenChangeList(cids[0]); Console.WriteLine("{0} reopened.", cl); }
/// <summary> /// Does all the work. /// </summary> /// <param name="args"></param> static void Main(string[] args) { if (args.Contains<string>("help") || args.Contains("-h") || args.Contains("-?")) { DisplayUsage(); return; } if (args.Length == 2 && args[0].Equals("testsetup", StringComparison.InvariantCultureIgnoreCase)) { TestSetup(args[1]); return; } int remindDaysOld = -1; if (args.Length == 2 && args[0].Equals("--remind", StringComparison.InvariantCultureIgnoreCase)) { if ((!Int32.TryParse(args[1], out remindDaysOld)) || (remindDaysOld <= 0)) { DisplayUsage(); return; } } if (args.Length > 0 && remindDaysOld <= 0) { SetConfiguration(args); return; } if (!ReviewNotifierConfiguration.Load()) return; logger = new Logger(Config.LogFile); logger.Log("Started processing review notifications @ {0}", DateTime.Now); MailTemplates templates = new MailTemplates(logger); CodeReviewDataContext context = new CodeReviewDataContext(Config.Database); Dictionary<int, string> sourceControlRoots = new Dictionary<int, string>(); var sourceControlsQuery = from sc in context.SourceControls select sc; foreach (SourceControl sc in sourceControlsQuery) { string site = string.Empty; if (!string.IsNullOrEmpty(sc.WebsiteName)) { if (!sc.WebsiteName.StartsWith("/")) site = "/" + sc.WebsiteName.Substring(1); else site = sc.WebsiteName; } sourceControlRoots[sc.Id] = site; } List<MessageType> exchangeItems = null; if (!string.IsNullOrEmpty(Config.EmailService)) exchangeItems = new List<MessageType>(); List<MailMessage> smtpItems = null; if (!string.IsNullOrEmpty(Config.SmtpServer)) smtpItems = new List<MailMessage>(); var mailChangeListQuery = from cl in context.MailChangeLists join rv in context.Reviewers on cl.ReviewerId equals rv.Id join ch in context.ChangeLists on cl.ChangeListId equals ch.Id select new { cl, rv.ReviewerAlias, ch.UserName, ch.SourceControlId, ch.CL, ch.Description }; foreach (var request in mailChangeListQuery) { logger.Log("Sending new review request for {0} to {1}", request.CL, request.ReviewerAlias); string subject = templates.CreateMailSubject(MailTemplates.MailType.Request, request.CL, request.UserName, Abbreviate(request.Description, MaxDescriptionLength), null); bool isBodyHtml; string body = templates.CreateMail(MailTemplates.MailType.Request, request.cl.ChangeListId, ResolveFriendlyName(Config, request.ReviewerAlias), ResolveFriendlyName(Config, request.UserName), Config.WebServer, sourceControlRoots[request.SourceControlId], null, request.Description, request.CL, out isBodyHtml); try { string email = ResolveUser(Config, request.ReviewerAlias); string replyToAlias = ResolveUser(Config, request.UserName); string sender = ResolveUser(Config, Config.User); string from = Config.FromEmail == null ? null : Config.FromEmail + "@" + Config.EmailDomain; if (exchangeItems != null) { exchangeItems.Add(MakeExchangeMessage(email, from, replyToAlias, subject, body, isBodyHtml, request.cl.ChangeListId, true)); } if (smtpItems != null) { smtpItems.Add(MakeSmtpMessage(email, from, replyToAlias, sender, subject, body, isBodyHtml, request.cl.ChangeListId, true)); } } catch (FormatException) { logger.Log("Could not send email - invalid email format!"); } context.MailChangeLists.DeleteOnSubmit(request.cl); } var reviewInviteQuery = (from ri in context.MailReviewRequests join ch in context.ChangeLists on ri.ChangeListId equals ch.Id select new { ri, ch }).ToArray(); foreach (var invite in reviewInviteQuery) { logger.Log("Sending new review invitation for {0} to {1}", invite.ch.CL, invite.ri.ReviewerAlias); string subject = templates.CreateMailSubject(MailTemplates.MailType.Invite, invite.ch.CL, invite.ch.UserName, Abbreviate(invite.ch.Description, MaxDescriptionLength), null); bool isBodyHtml; string body = templates.CreateMail(MailTemplates.MailType.Invite, invite.ch.Id, null, ResolveFriendlyName(Config, invite.ch.UserName), Config.WebServer, sourceControlRoots[invite.ch.SourceControlId], null, invite.ch.Description, invite.ch.CL, out isBodyHtml); try { string email = ResolveUser(Config, invite.ri.ReviewerAlias); string replyToAlias = ResolveUser(Config, invite.ch.UserName); string sender = ResolveUser(Config, Config.User); string from = Config.FromEmail == null ? null : Config.FromEmail + "@" + Config.EmailDomain; if (exchangeItems != null) exchangeItems.Add(MakeExchangeMessage(email, from, replyToAlias, subject, body, isBodyHtml, invite.ch.Id, true)); if (smtpItems != null) smtpItems.Add(MakeSmtpMessage(email, from, replyToAlias, sender, subject, body, isBodyHtml, invite.ch.Id, true)); } catch (FormatException) { logger.Log("Could not send email - invalid email format!"); } context.MailReviewRequests.DeleteOnSubmit(invite.ri); } var mailReviewQuery = (from mr in context.MailReviews join rw in context.Reviews on mr.ReviewId equals rw.Id join cc in context.ChangeLists on rw.ChangeListId equals cc.Id select new { mr, rw, cc }).ToArray(); foreach (var request in mailReviewQuery) { logger.Log("Sending review notification for {0}", request.cc.CL); string from = Config.FromEmail == null ? null : Config.FromEmail + "@" + Config.EmailDomain; string sender = ResolveUser(Config, Config.User); List<string> to = new List<string>(); List<string> cc = new List<string>(); if (request.cc.UserName.Equals(request.rw.UserName, StringComparison.InvariantCultureIgnoreCase)) { // This is a response to review comments. The reviewers are on the 'to' line, reviewee is on 'cc'. cc.Add(ResolveUser(Config, request.cc.UserName)); foreach (Reviewer r in request.cc.Reviewers) to.Add(ResolveUser(Config, r.ReviewerAlias)); } else { // This is a review. The reviewee is on the 'to' line, the reviewers are on 'cc'. to.Add(ResolveUser(Config, request.cc.UserName)); foreach (Reviewer r in request.cc.Reviewers) cc.Add(ResolveUser(Config, r.ReviewerAlias)); } string replyToAlias = ResolveUser(Config, request.rw.UserName); string malevichUrl = "http://" + Config.WebServer + sourceControlRoots[request.cc.SourceControlId] + "/default.aspx"; StringBuilder comments = new StringBuilder(); string body; bool isBodyHtml; string subject; if (request.cc.UserName.Equals(request.rw.UserName, StringComparison.InvariantCultureIgnoreCase)) { subject = templates.CreateMailSubject(MailTemplates.MailType.Response, request.cc.CL, request.cc.UserName, Abbreviate(request.cc.Description, MaxDescriptionLength), null); isBodyHtml = templates.IsTemplateHtml(MailTemplates.MailType.Response); ListCommentsForReview(context, request.rw, comments, isBodyHtml, malevichUrl); body = templates.CreateMail(MailTemplates.MailType.Response, request.cc.Id, null, ResolveFriendlyName(Config, request.rw.UserName), Config.WebServer, sourceControlRoots[request.cc.SourceControlId], null, comments.ToString(), request.cc.CL); } else { string verdict = ReviewStatusToSentence(request.rw.OverallStatus); subject = templates.CreateMailSubject(MailTemplates.MailType.Iteration, request.cc.CL, request.cc.UserName, Abbreviate(request.cc.Description, MaxDescriptionLength), verdict); isBodyHtml = templates.IsTemplateHtml(MailTemplates.MailType.Iteration); ListCommentsForReview(context, request.rw, comments, isBodyHtml, malevichUrl); body = templates.CreateMail(MailTemplates.MailType.Iteration, request.cc.Id, ResolveFriendlyName(Config, request.rw.UserName), ResolveFriendlyName(Config, request.cc.UserName), Config.WebServer, sourceControlRoots[request.cc.SourceControlId], verdict, comments.ToString(), request.cc.CL); } try { if (exchangeItems != null) exchangeItems.Add(MakeExchangeMessage(to, cc, from, replyToAlias, subject, body.ToString(), isBodyHtml, request.cc.Id, false)); if (smtpItems != null) smtpItems.Add(MakeSmtpMessage(to, cc, from, replyToAlias, sender, subject, body.ToString(), isBodyHtml, request.cc.Id, false)); } catch (FormatException) { logger.Log("Could not send email - invalid email format!"); } context.MailReviews.DeleteOnSubmit(request.mr); } if (remindDaysOld > 0) { DateTime threshold = DateTime.Now.AddDays(-remindDaysOld); var oldChangeListQuery = from rr in context.ChangeLists where rr.Stage == 0 && rr.TimeStamp < threshold select rr; foreach (ChangeList cl in oldChangeListQuery) { logger.Log("Sending review reminder for {0}", cl.CL); string subject = templates.CreateMailSubject(MailTemplates.MailType.Reminder, cl.CL, cl.UserName, Abbreviate(cl.Description, MaxDescriptionLength), null); string email = ResolveUser(Config, cl.UserName); string sender = ResolveUser(Config, Config.User); string from = Config.FromEmail == null ? null : Config.FromEmail + "@" + Config.EmailDomain; bool isBodyHtml; string body = templates.CreateMail(MailTemplates.MailType.Reminder, cl.Id, null, ResolveFriendlyName(Config, cl.UserName), Config.WebServer, sourceControlRoots[cl.SourceControlId], null, cl.Description, cl.CL, out isBodyHtml); try { if (exchangeItems != null) exchangeItems.Add(MakeExchangeMessage(email, from, null, subject, body, isBodyHtml, cl.Id, false)); if (smtpItems != null) smtpItems.Add(MakeSmtpMessage(email, from, null, sender, subject, body, isBodyHtml, cl.Id, false)); } catch (FormatException) { logger.Log("Could not send email - invalid email format!"); } } } if (exchangeItems != null && exchangeItems.Count() > 0) SendExchangeMail(Config, exchangeItems); if (smtpItems != null && smtpItems.Count() > 0) SendSmtpMail(Config, smtpItems); context.SubmitChanges(); logger.Log("Finished processing review mail @ {0}", DateTime.Now); logger.Close(); }
public int GetNumberOfOpenReviews() { if (!HttpContext.Current.User.Identity.IsAuthenticated) return 0; CodeReviewDataContext dataContext = new CodeReviewDataContext( System.Configuration.ConfigurationManager.ConnectionStrings[Config.ConnectionString].ConnectionString); int result = (from cc in dataContext.ChangeLists where cc.Stage == 0 select cc).Distinct().Count(); dataContext.Connection.Close(); dataContext.Dispose(); return result; }