1
0

graphlog.py 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253
  1. import time
  2. import util
  3. # Mercurial's graphlog code -------------------------------------------------------
  4. def asciiedges(seen, rev, parents):
  5. """adds edge info to changelog DAG walk suitable for ascii()"""
  6. if rev not in seen:
  7. seen.append(rev)
  8. nodeidx = seen.index(rev)
  9. knownparents = []
  10. newparents = []
  11. for parent in parents:
  12. if parent in seen:
  13. knownparents.append(parent)
  14. else:
  15. newparents.append(parent)
  16. ncols = len(seen)
  17. seen[nodeidx:nodeidx + 1] = newparents
  18. edges = [(nodeidx, seen.index(p)) for p in knownparents]
  19. if len(newparents) > 0:
  20. edges.append((nodeidx, nodeidx))
  21. if len(newparents) > 1:
  22. edges.append((nodeidx, nodeidx + 1))
  23. nmorecols = len(seen) - ncols
  24. return nodeidx, edges, ncols, nmorecols
  25. def get_nodeline_edges_tail(
  26. node_index, p_node_index, n_columns, n_columns_diff, p_diff, fix_tail):
  27. if fix_tail and n_columns_diff == p_diff and n_columns_diff != 0:
  28. # Still going in the same non-vertical direction.
  29. if n_columns_diff == -1:
  30. start = max(node_index + 1, p_node_index)
  31. tail = ["|", " "] * (start - node_index - 1)
  32. tail.extend(["/", " "] * (n_columns - start))
  33. return tail
  34. else:
  35. return ["\\", " "] * (n_columns - node_index - 1)
  36. else:
  37. return ["|", " "] * (n_columns - node_index - 1)
  38. def draw_edges(edges, nodeline, interline):
  39. for (start, end) in edges:
  40. if start == end + 1:
  41. interline[2 * end + 1] = "/"
  42. elif start == end - 1:
  43. interline[2 * start + 1] = "\\"
  44. elif start == end:
  45. interline[2 * start] = "|"
  46. else:
  47. nodeline[2 * end] = "+"
  48. if start > end:
  49. (start, end) = (end, start)
  50. for i in range(2 * start + 1, 2 * end):
  51. if nodeline[i] != "+":
  52. nodeline[i] = "-"
  53. def fix_long_right_edges(edges):
  54. for (i, (start, end)) in enumerate(edges):
  55. if end > start:
  56. edges[i] = (start, end + 1)
  57. def ascii(state, type, char, text, coldata, verbose):
  58. """prints an ASCII graph of the DAG
  59. takes the following arguments (one call per node in the graph):
  60. - Somewhere to keep the needed state in (init to asciistate())
  61. - Column of the current node in the set of ongoing edges.
  62. - Type indicator of node data == ASCIIDATA.
  63. - Payload: (char, lines):
  64. - Character to use as node's symbol.
  65. - List of lines to display as the node's text.
  66. - Edges; a list of (col, next_col) indicating the edges between
  67. the current node and its parents.
  68. - Number of columns (ongoing edges) in the current revision.
  69. - The difference between the number of columns (ongoing edges)
  70. in the next revision and the number of columns (ongoing edges)
  71. in the current revision. That is: -1 means one column removed;
  72. 0 means no columns added or removed; 1 means one column added.
  73. - Verbosity: if enabled then the graph prints an extra '|'
  74. between each line of information.
  75. Returns a string representing the output.
  76. """
  77. idx, edges, ncols, coldiff = coldata
  78. assert -2 < coldiff < 2
  79. if coldiff == -1:
  80. # Transform
  81. #
  82. # | | | | | |
  83. # o | | into o---+
  84. # |X / |/ /
  85. # | | | |
  86. fix_long_right_edges(edges)
  87. # add_padding_line says whether to rewrite
  88. #
  89. # | | | | | | | |
  90. # | o---+ into | o---+
  91. # | / / | | | # <--- padding line
  92. # o | | | / /
  93. # o | |
  94. add_padding_line = (len(text) > 2 and coldiff == -1 and
  95. [x for (x, y) in edges if x + 1 < y] and
  96. verbose)
  97. # fix_nodeline_tail says whether to rewrite
  98. #
  99. # | | o | | | | o | |
  100. # | | |/ / | | |/ /
  101. # | o | | into | o / / # <--- fixed nodeline tail
  102. # | |/ / | |/ /
  103. # o | | o | |
  104. fix_nodeline_tail = len(text) <= 2 and not add_padding_line
  105. # nodeline is the line containing the node character (typically o)
  106. nodeline = ["|", " "] * idx
  107. nodeline.extend([char, " "])
  108. nodeline.extend(
  109. get_nodeline_edges_tail(idx, state[1], ncols, coldiff,
  110. state[0], fix_nodeline_tail))
  111. # shift_interline is the line containing the non-vertical
  112. # edges between this entry and the next
  113. shift_interline = ["|", " "] * idx
  114. if coldiff == -1:
  115. n_spaces = 1
  116. edge_ch = "/"
  117. elif coldiff == 0:
  118. n_spaces = 2
  119. edge_ch = "|"
  120. else:
  121. n_spaces = 3
  122. edge_ch = "\\"
  123. shift_interline.extend(n_spaces * [" "])
  124. shift_interline.extend([edge_ch, " "] * (ncols - idx - 1))
  125. # draw edges from the current node to its parents
  126. draw_edges(edges, nodeline, shift_interline)
  127. # lines is the list of all graph lines to print
  128. lines = [nodeline]
  129. if add_padding_line:
  130. lines.append(get_padding_line(idx, ncols, edges))
  131. lines.append(shift_interline)
  132. # make sure that there are as many graph lines as there are
  133. # log strings
  134. if any("/" in s for s in lines) or verbose:
  135. while len(text) < len(lines):
  136. text.append('')
  137. if len(lines) < len(text):
  138. extra_interline = ["|", " "] * (ncols + coldiff)
  139. while len(lines) < len(text):
  140. lines.append(extra_interline)
  141. indentation_level = max(ncols, ncols + coldiff)
  142. result = []
  143. for (line, logstr) in zip(lines, text):
  144. graph = "%-*s" % (2 * indentation_level, "".join(line))
  145. if not graph.isspace():
  146. result.append([graph, logstr])
  147. # ... and start over
  148. state[0] = coldiff
  149. state[1] = idx
  150. return result
  151. def generate(verbose, num_header_lines, first_visible_line, last_visible_line, inline_graph, nodesData):
  152. """
  153. Generate an array of the graph, and text describing the node of the graph.
  154. """
  155. seen, state = [], [0, 0]
  156. result = []
  157. current = nodesData.current()
  158. nodes, nmap = nodesData.make_nodes()
  159. for node in nodes:
  160. node.children = [n for n in nodes if n.parent == node]
  161. def walk_nodes(nodes):
  162. for node in nodes:
  163. if node.parent:
  164. yield (node, [node.parent])
  165. else:
  166. yield (node, [])
  167. dag = sorted(nodes, key=lambda n: int(n.n), reverse=True)
  168. dag = walk_nodes(dag)
  169. line_number = num_header_lines
  170. for idx, part in list(enumerate(dag)):
  171. node, parents = part
  172. if node.time:
  173. age_label = age(int(node.time))
  174. else:
  175. age_label = 'Original'
  176. line = '[%s] %s' % (node.n, age_label)
  177. if node.n == current:
  178. char = '@'
  179. elif node.saved:
  180. char = 'w'
  181. else:
  182. char = 'o'
  183. show_inine_diff = inline_graph and line_number >= first_visible_line and line_number <= last_visible_line
  184. preview_diff = nodesData.preview_diff(node.parent, node,False,show_inine_diff)
  185. line = '[%s] %-10s %s' % (node.n, age_label, preview_diff)
  186. new_lines = ascii(state, 'C', char, [line], asciiedges(seen, node, parents), verbose)
  187. line_number += len(new_lines)
  188. result.extend(new_lines)
  189. util._undo_to(current)
  190. return result
  191. # Mercurial age function -----------------------------------------------------------
  192. agescales = [("yr", 3600 * 24 * 365),
  193. ("mon", 3600 * 24 * 30),
  194. ("wk", 3600 * 24 * 7),
  195. ("dy", 3600 * 24),
  196. ("hr", 3600),
  197. ("min", 60)]
  198. def age(ts):
  199. '''turn a timestamp into an age string.'''
  200. def plural(t, c):
  201. if c == 1:
  202. return t
  203. return t + "s"
  204. def fmt(t, c):
  205. return "%d %s" % (int(c), plural(t, c))
  206. now = time.time()
  207. then = ts
  208. if then > now:
  209. return 'in the future'
  210. delta = max(1, int(now - then))
  211. if delta > agescales[0][1] * 2:
  212. return time.strftime('%Y-%m-%d', time.gmtime(float(ts)))
  213. for t, s in agescales:
  214. n = delta // s
  215. if n >= 2 or s == 1:
  216. return '%s ago' % fmt(t, n)
  217. return "<1 min ago"