Inheritance: FileBaseCommandVerbOptions
Beispiel #1
0
        protected override bool RunInternal(Area ws, Versionr.Status status, IList <Versionr.Status.StatusEntry> targets, FileBaseCommandVerbOptions options)
        {
            LogVerbOptions localOptions = options as LogVerbOptions;

            if (JruntingMode)
            {
                localOptions.Detail = LogVerbOptions.DetailMode.Jrunting;
            }

            if (localOptions.ShowAutoMerges)
            {
                localOptions.ShowMerges = true;
            }

            if (localOptions.FollowBranches || localOptions.ShowMerges)
            {
                if (!localOptions.Logical)
                {
                    Printer.PrintError("#e#Error:## Following branches and specifically enabling display or merges are only valid options when showing the #b#--logical## history.");
                    return(false);
                }
            }

            Printer.EnableDiagnostics = localOptions.Verbose;

            bool targetedBranch = false;

            Objects.Version version = null;
            if (!string.IsNullOrEmpty(localOptions.Branch))
            {
                bool multipleBranches = false;
                var  branch           = ws.GetBranchByPartialName(localOptions.Branch, out multipleBranches);
                if (branch == null || multipleBranches)
                {
                    Printer.PrintError("No unique branch found for {0}", localOptions.Branch);
                    return(false);
                }
                version        = ws.GetBranchHeadVersion(branch);
                targetedBranch = true;
            }
            else if (!string.IsNullOrEmpty(localOptions.Version))
            {
                version = ws.GetPartialVersion(localOptions.Version);
                if (version == null)
                {
                    Printer.PrintError("Couldn't find matching version for {0}", localOptions.Version);
                    return(false);
                }
            }

            bool versionAutoSelected              = false;
            bool lastResortVersionSelection       = false;
            List <Objects.Head> targetHeadObjects = null;

            if (localOptions.Limit == -1)
            {
                localOptions.Limit = (version == null || targetedBranch) ? 10 : 1;
            }
            if (version == null)
            {
                versionAutoSelected = true;
                targetHeadObjects   = ws.GetBranchHeads(ws.CurrentBranch);
                if (targetHeadObjects.Count == 1)
                {
                    version = ws.GetVersion(targetHeadObjects[0].Version);
                }
                else
                {
                    var guid = ws.Version.ID;
                    foreach (var head in targetHeadObjects)
                    {
                        if (head.Version == guid)
                        {
                            version = ws.Version;
                            break;
                        }
                    }
                    if (version == null)
                    {
                        foreach (var head in targetHeadObjects)
                        {
                            var temphistory = ws.GetHistory(ws.GetVersion(head.Version), null);
                            foreach (var h in temphistory)
                            {
                                if (h.ID == guid)
                                {
                                    version = ws.GetVersion(head.Version);
                                    break;
                                }
                            }
                        }
                    }
                    if (version == null)
                    {
                        lastResortVersionSelection = true;
                        version = ws.Version;
                    }
                }
            }

            int?nullableLimit = localOptions.Limit;

            if (nullableLimit.Value <= 0)
            {
                nullableLimit = null;
            }

            if (localOptions.Xml)
            {
                Printer.PrintMessage("<?xml version='1.0'?>");
                Printer.PrintMessage($"<vsrlog>");
                var branch = ws.GetBranch(version.Branch);
                Printer.PrintMessage($"  <branch id='{branch.ID}' name='{XmlAttr(branch.Name)}'>");
                foreach (var head in ws.GetBranchHeads(branch))
                {
                    Printer.PrintMessage($"    <head version='{head.Version}' />");
                }
                Printer.PrintMessage("  </branch>");
            }

            var history = (localOptions.Logical ? ws.GetLogicalHistorySequenced(version, localOptions.FollowBranches, localOptions.ShowMerges, localOptions.ShowAutoMerges, nullableLimit) : ws.GetHistory(version, nullableLimit).Select(x => new Tuple <Objects.Version, int>(x, 0))).AsEnumerable();

            m_Tip = Workspace.Version;
            Objects.Version last = null;
            m_Branches = new Dictionary <Guid, Objects.Branch>();
            bool anything = false;

            ws.BeginDatabaseTransaction();
            foreach (var x in ApplyHistoryFilter(history, localOptions))
            {
                last = x.Item1.Item1;
                FormatLog(x.Item1, x.Item2, localOptions);
                anything = true;
            }
            ws.CommitDatabaseTransaction();

            if (localOptions.Xml)
            {
                Printer.PrintMessage("</vsrlog>");
            }
            else
            {
                if (!localOptions.Jrunting)
                {
                    if (last != null && last.ID != m_Tip.ID)
                    {
                        var  branch     = Workspace.CurrentBranch;
                        var  heads      = Workspace.GetBranchHeads(branch);
                        bool isHead     = heads.Any(x => x.Version == m_Tip.ID);
                        bool isOnlyHead = heads.Count == 1;
                        if (!isHead)
                        {
                            Printer.PrintMessage("\nCurrent version #b#{0}## is #e#not the head## of branch #b#{1}## (#b#\"{2}\"##)", m_Tip.ShortName, branch.ShortID, branch.Name);
                        }
                        else if (!isOnlyHead)
                        {
                            Printer.PrintMessage("\nCurrent version #b#{0}## is #w#not only the head## of branch #b#{1}## (#b#\"{2}\"##)", m_Tip.ShortName, branch.ShortID, branch.Name);
                        }
                    }
                    if (!anything)
                    {
                        if (!nullableLimit.HasValue || nullableLimit.Value <= 0)
                        {
                            Printer.PrintMessage("\nNo versions matched your history/filter query (searched #b#all## revisions).");
                        }
                        else
                        {
                            Printer.PrintMessage("\nNo versions matched your history/filter query (searched #b#{0}## revisions).\n\nTry setting #b#--limit## to a larger value (or #b#0## for all revisions).", nullableLimit.Value);
                        }
                    }
                }
                if (versionAutoSelected)
                {
                    if (targetHeadObjects.Count > 1)
                    {
                        Printer.WriteLineMessage("\n #w#Warning:## Target branch has multiple heads.");

                        Printer.WriteLineMessage("\n Heads of #b#\"{0}\"##:", ws.CurrentBranch.Name);
                        foreach (var x in targetHeadObjects)
                        {
                            var v = Workspace.GetVersion(x.Version);
                            Printer.WriteLineMessage("   #b#{0}##: {1} by {2}", v.ShortName, v.Timestamp.ToLocalTime(), v.Author);
                        }
                    }
                }
            }
            return(true);
        }
Beispiel #2
0
        private IEnumerable <Tuple <Tuple <Objects.Version, int>, IEnumerable <KeyValuePair <bool, ResolvedAlteration> > > > ApplyHistoryFilter(IEnumerable <Tuple <Objects.Version, int> > history, LogVerbOptions localOptions)
        {
            if (!string.IsNullOrEmpty(localOptions.Author))
            {
                history = history.Where(x => x.Item1.Author.Equals(localOptions.Author, StringComparison.OrdinalIgnoreCase));
            }

            var enumeration = history
                              .Select(x => new Tuple <Tuple <Objects.Version, int>, IEnumerable <KeyValuePair <bool, ResolvedAlteration> > >(x, FilterAlterations(x.Item1)))
                              .Where(x => x.Item2.Any() || (x.Item1.Item1.Parent == null || localOptions.Objects.Count == 0));

            if (localOptions.Limit != 0)
            {
                enumeration = enumeration.Take(localOptions.Limit);
            }

            if (!(localOptions.Jrunting ^ localOptions.Reverse))
            {
                enumeration = enumeration.Reverse();
            }

            return(enumeration);
        }
Beispiel #3
0
		private IEnumerable<Tuple<Objects.Version, IEnumerable<KeyValuePair<bool, ResolvedAlteration>>>> ApplyHistoryFilter(IEnumerable<Objects.Version> history, LogVerbOptions localOptions)
		{
			if (!string.IsNullOrEmpty(localOptions.Author))
				history = history.Where(x => x.Author.Equals(localOptions.Author, StringComparison.OrdinalIgnoreCase));

			var enumeration = history
				.Select(x => new Tuple<Objects.Version, IEnumerable<KeyValuePair<bool, ResolvedAlteration>>>(x, FilterAlterations(x)))
				.Where(x => x.Item2.Any() || (x.Item1.Parent == null || localOptions.Objects.Count == 0));

			if (localOptions.Limit != 0)
				enumeration = enumeration.Take(localOptions.Limit);

			if (!(localOptions.Jrunting ^ localOptions.Reverse))
				enumeration = enumeration.Reverse();

			return enumeration;
		}
Beispiel #4
0
        private void FormatLog(Tuple <Objects.Version, int> vt, IEnumerable <KeyValuePair <bool, ResolvedAlteration> > filteralt, LogVerbOptions localOptions)
        {
            Objects.Version v = vt.Item1;
            if (m_LoggedVersions == null)
            {
                m_LoggedVersions = new HashSet <Guid>();
            }
            m_LoggedVersions.Add(v.ID);

            Objects.Branch branch = null;
            if (!m_Branches.TryGetValue(v.Branch, out branch))
            {
                branch = Workspace.GetBranch(v.Branch);
                m_Branches[v.Branch] = branch;
            }

            if (localOptions.Xml)
            {
                Printer.PrintMessage($"  <version id='{v.ID}' parent='{v.Parent}' branch='{v.Branch}' timestamp='{v.Timestamp.ToString("o")}' author='{XmlAttr(v.Author)}' published='{v.Published}'>");
                Printer.PrintMessage($"    <message>{XmlText(v.Message)}</message>");

                foreach (var y in Workspace.GetMergeInfo(v.ID))
                {
                    var mergeParent = Workspace.GetVersion(y.SourceVersion);
                    Printer.PrintMessage($"    <merge type='{y.Type.ToString().ToLower()}' version='{mergeParent.ID}' branch='{mergeParent.Branch}' />");
                }

                if (localOptions.Detail == LogVerbOptions.DetailMode.Full)
                {
                    foreach (var y in GetAlterations(v))
                    {
                        string operationName = y.Alteration.Type.ToString().ToLower();
                        if (y.Alteration.Type == Objects.AlterationType.Copy || y.Alteration.Type == Objects.AlterationType.Move)
                        {
                            Objects.Record prior  = Workspace.GetRecord(y.Alteration.PriorRecord.Value);
                            Objects.Record next   = Workspace.GetRecord(y.Alteration.NewRecord.Value);
                            bool           edited = (!next.IsDirectory && prior.DataIdentifier != next.DataIdentifier);
                            Printer.PrintMessage($"    <alteration type='{operationName}' path='{XmlAttr(next.CanonicalName)}' frompath='{XmlAttr(prior.CanonicalName)}' edited='{edited}' />");
                        }
                        else
                        {
                            Printer.PrintMessage($"    <alteration type='{operationName}' path='{y.Record.CanonicalName}' />");
                        }
                    }
                }
                Printer.PrintMessage("  </version>");
            }
            else if (localOptions.Jrunting)
            {
                // list of heads
                var    heads      = Workspace.GetHeads(v.ID);
                bool   isHead     = false;
                string headString = "";
                foreach (var y in heads)
                {
                    isHead = true;
                    if (headString.Length != 0)
                    {
                        headString = headString + ", ";
                    }
                    headString += Workspace.GetBranch(y.Branch).Name;
                }

                // message up to first newline
                string message = v.Message;
                if (message == null)
                {
                    message = string.Empty;
                }

                message = message.Replace("\r\n", "\n");

                var idx = message.IndexOf('\n');
                if (idx == -1)
                {
                    idx = message.Length;
                }
                message = message.Substring(0, idx);


                string mergemarker = "";
                if (Workspace.GetMergeInfo(v.ID).Count() > 0)
                {
                    var m      = Workspace.GetMergeInfo(v.ID).First();
                    var heads2 = Workspace.GetHeads(m.SourceVersion);
                    if (heads2.Count > 0)
                    {
                        if (isHead)
                        {
                            mergemarker = " <- " + Workspace.GetBranch(heads2.First().Branch).Name;
                        }
                        else
                        {
                            mergemarker = "M: " + Workspace.GetBranch(heads2.First().Branch).Name;
                        }
                    }
                }

                var date = new DateTime(v.Timestamp.Ticks, DateTimeKind.Utc).ToShortDateString();

                string pattern = "* #U#{0}## - ";

                if (isHead)
                {
                    pattern += "#Y#({4}{5})## ";
                }
                else if (mergemarker.Length > 0)
                {
                    pattern += "#Y#({5})## ";
                }


                pattern += "{1} ";
                var tagList = Workspace.GetTagsForVersion(v.ID);
                if (tagList.Count > 0)
                {
                    pattern += "#I#[" + string.Join(" ", tagList.Select(x => "\\#" + x).ToArray()) + "]## ";
                }
                pattern += "#g#({2}, {3})##";

                Printer.PrintMessage(pattern, v.ShortName, message, v.Author, date, headString, mergemarker);
            }
            else if (localOptions.Concise)
            {
                if (vt.Item2 != 0 && localOptions.Indent)
                {
                    Printer.Prefix = " ";
                }
                var  heads  = Workspace.GetHeads(v.ID);
                bool isHead = false;
                foreach (var y in heads)
                {
                    if (y.Branch == branch.ID)
                    {
                        isHead = true;
                        break;
                    }
                }
                string message = v.Message;
                if (message == null)
                {
                    message = string.Empty;
                }
                string tipmarker = " ";
                if (v.ID == m_Tip.ID)
                {
                    tipmarker = "#w#*##";
                }
                string mergemarker = " ";
                if (Workspace.GetMergeInfo(v.ID).FirstOrDefault() != null)
                {
                    mergemarker = "#s#M##";
                }
                var    tagList = Workspace.GetTagsForVersion(v.ID);
                string tags    = "";
                if (tagList.Count > 0)
                {
                    tags = "#s#" + string.Join(" ", tagList.Select(x => "\\#" + x).ToArray()) + "## ";
                }
                Printer.PrintMessage($"{tipmarker}#c#{v.ShortName}:##{mergemarker}({v.Revision}/{(isHead ? "#i#" : "#b#")}{branch.Name}##)"
                                     + $"{message.Replace("\r\n", " ").Replace('\n', ' ')} {tags}"
                                     + $"#q#({v.Author} {new DateTime(v.Timestamp.Ticks, DateTimeKind.Utc).ToShortDateString()})##");
                Printer.Prefix = "";
            }
            else
            {
                Printer.PrintMessage("");
                if (vt.Item2 != 0 && localOptions.Indent)
                {
                    Printer.Prefix = "| ";
                }
                string tipmarker = "";
                if (v.ID == m_Tip.ID)
                {
                    tipmarker = " #w#*<current>##";
                }
                Printer.PrintMessage("({0}) #c#{1}## on branch #b#{2}##{3}", v.Revision, v.ID, branch.Name, tipmarker);

                var mergeInfo = Workspace.GetMergeInfo(v.ID);
                foreach (var y in mergeInfo)
                {
                    var            mergeParent = Workspace.GetVersion(y.SourceVersion);
                    Objects.Branch mergeBranch = null;
                    if (!m_Branches.TryGetValue(mergeParent.Branch, out mergeBranch))
                    {
                        mergeBranch = Workspace.GetBranch(mergeParent.Branch);
                        m_Branches[mergeParent.Branch] = mergeBranch;
                    }
                    Printer.PrintMessage(" <- Merged from #s#{0}## on branch #b#{1}##", mergeParent.ID, mergeBranch.Name);
                }

                var heads = Workspace.GetHeads(v.ID);
                foreach (var y in heads)
                {
                    Objects.Branch headBranch = null;
                    if (!m_Branches.TryGetValue(y.Branch, out headBranch))
                    {
                        headBranch           = Workspace.GetBranch(y.Branch);
                        m_Branches[y.Branch] = headBranch;
                    }
                    string branchFlags = string.Empty;
                    if (branch.Terminus.HasValue)
                    {
                        branchFlags = " #e#(deleted)##";
                    }
                    Printer.PrintMessage(" ++ #i#Head## of branch #b#{0}## (#b#\"{1}\"##){2}", headBranch.ID, headBranch.Name, branchFlags);
                }
                if (branch.Terminus == v.ID)
                {
                    Printer.PrintMessage(" ++ #i#Terminus## of #e#deleted branch## #b#{0}## (#b#\"{1}\"##)", branch.ID, branch.Name);
                }

                Printer.PrintMessage("#b#Author:## {0} #q# {1} ##", v.Author, v.Timestamp.ToLocalTime());
                var tagList = Workspace.GetTagsForVersion(v.ID);
                if (tagList.Count > 0)
                {
                    Printer.PrintMessage(" #s#" + string.Join(" ", tagList.Select(x => "\\#" + x).ToArray()) + "##");
                }
                Printer.PrintMessage("");
                Printer.PushIndent();
                Printer.PrintMessage("{0}", string.IsNullOrWhiteSpace(v.Message) ? "<none>" : Printer.Escape(v.Message));
                Printer.PopIndent();

                if (localOptions.Detail == LogVerbOptions.DetailMode.Detailed || localOptions.Detail == LogVerbOptions.DetailMode.Full)
                {
                    var alterations = localOptions.Detail == LogVerbOptions.DetailMode.Detailed ? filteralt.Select(z => z.Value) : GetAlterations(v);
                    if (localOptions.Detail == LogVerbOptions.DetailMode.Full)
                    {
                        Printer.PrintMessage("");
                        Printer.PrintMessage("#b#Alterations:##");
                        foreach (var y in alterations.OrderBy(z => z.Alteration.Type))
                        {
                            if (y.Alteration.Type == Objects.AlterationType.Move || y.Alteration.Type == Objects.AlterationType.Copy)
                            {
                                string         operationName = y.Alteration.Type.ToString().ToLower();
                                Objects.Record prior         = Workspace.GetRecord(y.Alteration.PriorRecord.Value);
                                Objects.Record next          = Workspace.GetRecord(y.Alteration.NewRecord.Value);
                                bool           isUpdate      = false;
                                if (y.Alteration.Type == Objects.AlterationType.Move && !next.IsDirectory && prior.DataIdentifier != next.DataIdentifier)
                                {
                                    isUpdate      = true;
                                    operationName = "refactor";
                                }
                                Printer.PrintMessage("#{2}#({0})## {1}\n  <- #q#{3}##", operationName, y.Record.CanonicalName, GetAlterationFormat(y.Alteration.Type), prior.CanonicalName);
                                if (localOptions.Diff && isUpdate)
                                {
                                    InlineDiff(prior, next);
                                }
                            }
                            else
                            {
                                Printer.PrintMessage("#{2}#({0})## {1}", y.Alteration.Type.ToString().ToLower(), y.Record.CanonicalName, GetAlterationFormat(y.Alteration.Type));
                                if (localOptions.Diff && y.Alteration.Type == Objects.AlterationType.Update)
                                {
                                    InlineDiff(Workspace.GetRecord(y.Alteration.PriorRecord.Value), Workspace.GetRecord(y.Alteration.NewRecord.Value));
                                }
                            }
                        }
                    }
                    else
                    {
                        int[] alterationCounts = new int[5];
                        foreach (var y in alterations)
                        {
                            alterationCounts[(int)y.Alteration.Type]++;
                        }
                        bool   first      = true;
                        string formatData = "";
                        for (int i = 0; i < alterationCounts.Length; i++)
                        {
                            if (alterationCounts[i] != 0)
                            {
                                if (!first)
                                {
                                    formatData += ", ";
                                }
                                else
                                {
                                    formatData += "  ";
                                }
                                first       = false;
                                formatData += string.Format("#{2}#{0}s: {1}##", ((Objects.AlterationType)i).ToString(), alterationCounts[i], GetAlterationFormat((Objects.AlterationType)i));
                            }
                        }
                        if (formatData.Length > 0)
                        {
                            Printer.PrintMessage("");
                            Printer.PrintMessage("#b#Alterations:##");
                            Printer.PrintMessage(formatData);
                        }
                    }
                }
                else if (FilterOptions.Objects.Count != 0)
                {
                    Printer.PrintMessage("");
                    Printer.PrintMessage("#b#Alterations:##");
                    List <KeyValuePair <string, ResolvedAlteration> > altList = new List <KeyValuePair <string, ResolvedAlteration> >();
                    foreach (var y in GetAlterations(v))
                    {
                        string recName = y.Record.CanonicalName;
                        altList.Add(new KeyValuePair <string, ResolvedAlteration>(recName, y));
                    }

                    if (localOptions.Diff)
                    {
                        var records = FilterObjects(altList)
                                      .SelectMany(x => new[] { x.Value.Alteration.PriorRecord, x.Value.Alteration.NewRecord })
                                      .Where(x => x.HasValue)
                                      .Select(x => Workspace.GetRecord(x.Value));

                        Workspace.GetMissingObjects(records, null);
                    }

                    foreach (var y in FilterObjects(altList).Select(x => x.Value))
                    {
                        if (y.Alteration.Type == Objects.AlterationType.Move || y.Alteration.Type == Objects.AlterationType.Copy)
                        {
                            string         operationName = y.Alteration.Type.ToString().ToLower();
                            Objects.Record prior         = Workspace.GetRecord(y.Alteration.PriorRecord.Value);
                            Objects.Record next          = Workspace.GetRecord(y.Alteration.NewRecord.Value);
                            bool           isUpdate      = false;
                            if (y.Alteration.Type == Objects.AlterationType.Move && !next.IsDirectory && prior.DataIdentifier != next.DataIdentifier)
                            {
                                isUpdate      = true;
                                operationName = "refactor";
                            }

                            Printer.PrintMessage("#{2}#({0})## {1}\n  <- #q#{3}##", operationName, y.Record.CanonicalName, GetAlterationFormat(y.Alteration.Type), prior.CanonicalName);

                            if (localOptions.Diff && isUpdate)
                            {
                                InlineDiff(prior, next);
                            }
                        }
                        else
                        {
                            Printer.PrintMessage("#{2}#({0})## {1}", y.Alteration.Type.ToString().ToLower(), y.Record.CanonicalName, GetAlterationFormat(y.Alteration.Type));
                            if (localOptions.Diff && y.Alteration.Type == Objects.AlterationType.Update)
                            {
                                InlineDiff(Workspace.GetRecord(y.Alteration.PriorRecord.Value), Workspace.GetRecord(y.Alteration.NewRecord.Value));
                            }
                        }
                    }
                }

                Printer.Prefix = "";
                // Same-branch merge revisions. This only sort-of respects the limit :(
                //foreach (var y in mergeInfo)
                //{
                //	var mergeParent = Workspace.GetVersion(y.SourceVersion);
                //	if (mergeParent.Branch == v.Branch)
                //	{
                //		Printer.PushIndent();
                //		Printer.PrintMessage("---- Merged versions ----");

                //		List<Objects.Version> mergedVersions = new List<Objects.Version>();

                //		var p = mergeParent;
                //		do
                //		{
                //			mergedVersions.Add(p);
                //			if (p.Parent.HasValue && !m_LoggedVersions.Contains(p.Parent.Value))
                //				p = Workspace.GetVersion(p.Parent.Value);
                //			else
                //				p = null;
                //		} while (p != null);

                //		foreach (var a in ApplyHistoryFilter(mergedVersions, localOptions))
                //			FormatLog(a.Item1, a.Item2, localOptions);

                //		Printer.PrintMessage("-------------------------");
                //		Printer.PopIndent();
                //	}
                //}
            }
        }
Beispiel #5
0
		private void FormatLog(Objects.Version v, IEnumerable<KeyValuePair<bool, ResolvedAlteration>> filteralt, LogVerbOptions localOptions)
		{
			if (m_LoggedVersions == null)
				m_LoggedVersions = new HashSet<Guid>();
			m_LoggedVersions.Add(v.ID);

            Objects.Branch branch = null;
			if (!m_Branches.TryGetValue(v.Branch, out branch))
			{
				branch = Workspace.GetBranch(v.Branch);
				m_Branches[v.Branch] = branch;
			}

			if (localOptions.Jrunting)
			{
				// list of heads
				var heads = Workspace.GetHeads(v.ID);
				bool isHead = false;
				string headString = "";
				foreach (var y in heads)
				{
					isHead = true;
					if (headString.Length != 0)
						headString = headString + ", ";
					headString += Workspace.GetBranch(y.Branch).Name;
				}

				// message up to first newline
				string message = v.Message;
				if (message == null)
					message = string.Empty;
				var idx = message.IndexOf('\n');
				if (idx == -1)
					idx = message.Length;
				message = message.Substring(0, idx);


				string mergemarker = "";
				if (Workspace.GetMergeInfo(v.ID).Count() > 0)
				{
					var m = Workspace.GetMergeInfo(v.ID).First();
					var heads2 = Workspace.GetHeads(m.SourceVersion);
					if (heads2.Count > 0)
						if (isHead)
							mergemarker = " <- " + Workspace.GetBranch(heads2.First().Branch).Name;
						else
							mergemarker = "M: " + Workspace.GetBranch(heads2.First().Branch).Name;
				}

				var date = new DateTime(v.Timestamp.Ticks, DateTimeKind.Utc).ToShortDateString();

				string pattern = "* #U#{0}## - ";

				if (isHead)
					pattern += "#Y#({4}{5})## ";
					else if (mergemarker.Length > 0)
						pattern += "#Y#({5})## ";

				pattern += "{1} #g#({2}, {3})##";
				Printer.PrintMessage(pattern, v.ShortName, message, v.Author, date, headString, mergemarker);
			}
			else if (localOptions.Concise)
			{
				var heads = Workspace.GetHeads(v.ID);
				bool isHead = false;
				foreach (var y in heads)
				{
					if (y.Branch == branch.ID)
					{
						isHead = true;
						break;
					}
				}
				string message = v.Message;
				if (message == null)
					message = string.Empty;
				string tipmarker = " ";
				if (v.ID == m_Tip.ID)
					tipmarker = "#w#*##";
				string mergemarker = " ";
				if (Workspace.GetMergeInfo(v.ID).FirstOrDefault() != null)
					mergemarker = "#s#M##";
				Printer.PrintMessage("{6}#c#{0}:##{7}({4}/{8}{5}##) {1} #q#({2} {3})##", v.ShortName, message.Replace('\n', ' '), v.Author, new DateTime(v.Timestamp.Ticks, DateTimeKind.Utc).ToShortDateString(), v.Revision, branch.Name, tipmarker, mergemarker, isHead ? "#i#" : "#b#");
			}
			else
			{
				string tipmarker = "";
				if (v.ID == m_Tip.ID)
					tipmarker = " #w#*<current>##";
				Printer.PrintMessage("\n({0}) #c#{1}## on branch #b#{2}##{3}", v.Revision, v.ID, branch.Name, tipmarker);

				var mergeInfo = Workspace.GetMergeInfo(v.ID);
				foreach (var y in mergeInfo)
				{
					var mergeParent = Workspace.GetVersion(y.SourceVersion);
					Objects.Branch mergeBranch = null;
					if (!m_Branches.TryGetValue(mergeParent.Branch, out mergeBranch))
					{
						mergeBranch = Workspace.GetBranch(mergeParent.Branch);
						m_Branches[mergeParent.Branch] = mergeBranch;
					}
					Printer.PrintMessage(" <- Merged from #s#{0}## on branch #b#{1}##", mergeParent.ID, mergeBranch.Name);
				}

				var heads = Workspace.GetHeads(v.ID);
				foreach (var y in heads)
				{
					Objects.Branch headBranch = null;
					if (!m_Branches.TryGetValue(y.Branch, out headBranch))
					{
						headBranch = Workspace.GetBranch(y.Branch);
						m_Branches[y.Branch] = headBranch;
					}
					string branchFlags = string.Empty;
					if (branch.Terminus.HasValue)
						branchFlags = " #e#(deleted)##";
					Printer.PrintMessage(" ++ #i#Head## of branch #b#{0}## (#b#\"{1}\"##){2}", headBranch.ID, headBranch.Name, branchFlags);
				}
				if (branch.Terminus == v.ID)
					Printer.PrintMessage(" ++ #i#Terminus## of #e#deleted branch## #b#{0}## (#b#\"{1}\"##)", branch.ID, branch.Name);

				Printer.PrintMessage("#b#Author:## {0} #q# {1} ##\n", v.Author, v.Timestamp.ToLocalTime());
				Printer.PushIndent();
				Printer.PrintMessage("{0}", string.IsNullOrWhiteSpace(v.Message) ? "<none>" : Printer.Escape(v.Message));
				Printer.PopIndent();

				if (localOptions.Detail == LogVerbOptions.DetailMode.Detailed || localOptions.Detail == LogVerbOptions.DetailMode.Full)
				{
					var alterations = localOptions.Detail == LogVerbOptions.DetailMode.Detailed ? filteralt.Select(z => z.Value) : GetAlterations(v);
					if (localOptions.Detail == LogVerbOptions.DetailMode.Full)
					{
						Printer.PrintMessage("");
						Printer.PrintMessage("#b#Alterations:##");
						foreach (var y in alterations.OrderBy(z => z.Alteration.Type))
						{
                            if (y.Alteration.Type == Objects.AlterationType.Move || y.Alteration.Type == Objects.AlterationType.Copy)
                            {
                                string operationName = y.Alteration.Type.ToString().ToLower();
                                Objects.Record prior = Workspace.GetRecord(y.Alteration.PriorRecord.Value);
                                Objects.Record next = Workspace.GetRecord(y.Alteration.NewRecord.Value);
                                if (y.Alteration.Type == Objects.AlterationType.Move && !next.IsDirectory && prior.DataIdentifier != next.DataIdentifier)
                                    operationName = "refactor";
                                Printer.PrintMessage("#{2}#({0})## {1}\n  <- #q#{3}##", operationName, y.Record.CanonicalName, GetAlterationFormat(y.Alteration.Type), prior.CanonicalName);
                            }
                            else
                            {
                                Printer.PrintMessage("#{2}#({0})## {1}", y.Alteration.Type.ToString().ToLower(), y.Record.CanonicalName, GetAlterationFormat(y.Alteration.Type));
                            }
                        }
                    }
					else
					{
						int[] alterationCounts = new int[5];
						foreach (var y in alterations)
							alterationCounts[(int)y.Alteration.Type]++;
						bool first = true;
						string formatData = "";
						for (int i = 0; i < alterationCounts.Length; i++)
						{
							if (alterationCounts[i] != 0)
							{
								if (!first)
									formatData += ", ";
								else
									formatData += "  ";
								first = false;
								formatData += string.Format("#{2}#{0}s: {1}##", ((Objects.AlterationType)i).ToString(), alterationCounts[i], GetAlterationFormat((Objects.AlterationType)i));
							}
						}
						if (formatData.Length > 0)
						{
							Printer.PrintMessage("");
							Printer.PrintMessage("#b#Alterations:##");
							Printer.PrintMessage(formatData);
						}
					}
				}

				// Same-branch merge revisions. This only sort-of respects the limit :(
				//foreach (var y in mergeInfo)
				//{
				//	var mergeParent = Workspace.GetVersion(y.SourceVersion);
				//	if (mergeParent.Branch == v.Branch)
				//	{
				//		Printer.PushIndent();
				//		Printer.PrintMessage("---- Merged versions ----");

				//		List<Objects.Version> mergedVersions = new List<Objects.Version>();

				//		var p = mergeParent;
				//		do
				//		{
				//			mergedVersions.Add(p);
				//			if (p.Parent.HasValue && !m_LoggedVersions.Contains(p.Parent.Value))
				//				p = Workspace.GetVersion(p.Parent.Value);
				//			else
				//				p = null;
				//		} while (p != null);

				//		foreach (var a in ApplyHistoryFilter(mergedVersions, localOptions))
				//			FormatLog(a.Item1, a.Item2, localOptions);

				//		Printer.PrintMessage("-------------------------");
				//		Printer.PopIndent();
				//	}
				//}
			}
		}