/// <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(); }
/// <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> /// 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> /// 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> /// 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."); } }