├── README ├── flat.svg ├── gitcheat.png └── shapes.py /README: -------------------------------------------------------------------------------- 1 | Git Supervisual Cheatsheet 2 | =========================== 3 | 4 | Being a visual person I wanted a visual cheatsheet for git. 5 | 6 | I'll probably edit as I forget/learn commands. 7 | 8 | Send comments my way. 9 | 10 | matthewharrison@gmail.com 11 | 12 | licensed under cc by-sa 13 | -------------------------------------------------------------------------------- /gitcheat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/mattharrison/Git-Supervisual-Cheatsheet/e81ff7f078ab3069e24802046b2db9210e21d5f2/gitcheat.png -------------------------------------------------------------------------------- /shapes.py: -------------------------------------------------------------------------------- 1 | import math 2 | 3 | from pysvg.core import BaseElement 4 | from pysvg.structure import defs, svg, g 5 | from pysvg.shape import circle, line, path, rect 6 | from pysvg.text import text 7 | from pysvg.builders import ShapeBuilder, StyleBuilder, TransformBuilder 8 | 9 | 10 | INKSCAPE_HACK = True # apparently markers tweak backwards in inkscape 11 | 12 | # lowest num is lighter 13 | ALPHA = 'none' 14 | GREEN_1 = '#8ae234' 15 | GREEN_2 = '#73d216' 16 | GREEN_3 = '#4e9a06' 17 | 18 | RED_3 = '#a40000' 19 | 20 | PUR_3 = '#5c3566' 21 | PUR_2 = '#75507b' 22 | PUR_1 = '#ad7fa8' 23 | 24 | ORANGE_3 = '#ce5c00' 25 | BLACK = '#000000' 26 | 27 | ALUM_6 = '#2e3436' 28 | ALUM_5 = '#555753' 29 | ALUM_4 = '#888a85' 30 | ALUM_3 = '#babdb6' 31 | ALUM_2 = '#d3d7cf' 32 | ALUM_1 = '#eeeeec' 33 | HALF = '88' 34 | 35 | def local_work(): 36 | x = 0 37 | y = 0 38 | elems = [] 39 | repo_line = rev_path(x,y, x+600,y, color=PUR_3) 40 | elems.extend([repo_line, words('repository')]) 41 | x+= 20 42 | idx_line = rev_path(x,y, x+600,y, color=PUR_2) 43 | elems.extend([idx_line, words('"index"/staged files')]) 44 | x+= 20 45 | work_line = rev_path(x,y, x+600,y, color=PUR_1) 46 | elems.extend([work_line, words('work directory')]) 47 | x+= 20 48 | stash_line = rev_path(x,y, x+600,y, color=ALUM_2) 49 | elems.extend([stash_line, words('"stash"')]) 50 | return elems 51 | 52 | 53 | def branch_section2(): 54 | x = 0 55 | y = 0 56 | elems = [] 57 | elems.extend(rev_path(x,y, x+25,7)) 58 | # down to right 59 | elems.extend(rev_path(x+25, y, x+75, y+50, "fixme")) 60 | # up to right 61 | elems.extend(rev_path(x+75, y+50, x+125, y, "")) 62 | # across 63 | elems.extend(rev_path(x+25, y, x+125, y, "master")) 64 | 65 | elems.append(Thing(revision()).move(x+25, y)) 66 | elems.append(Thing(revision()).move(x+75, y+50)) 67 | elems.append(Thing(revision()).move(x+125, y)) 68 | 69 | print "ELEMS", elems 70 | elems.extend(between(x+75, 0, x+75, 50)) 71 | #cap_elems = triangle(-1,1, 0,3, 1,1, RED_3+HALF) 72 | if INKSCAPE_HACK: 73 | cap_elems = triangle(0,1, -2,0, 0,-1, RED_3) 74 | else: 75 | cap_elems = triangle(0,1, 2,0, 0,-1, RED_3) 76 | elems.append(cap_def('RED_3END', cap_elems)) 77 | elems.extend(arc_curve(x+100, 0, x+100, 70, cap_def='RED_3END')) 78 | 79 | # small numbers 80 | elems.append(Thing(num('1', GREEN_3)).move(x+15, y+10)) 81 | elems.append(Thing(num('2', GREEN_3)).move(x+35, y+30)) 82 | elems.append(Thing(num('3', GREEN_3)).move(x+50, y+45)) 83 | 84 | # large numbers 85 | elems.append(Thing(command('1', 86 | 'git checkout -t fixme', 87 | 'use -t to set up remote tracking', 88 | GREEN_3)).move(x, y+70)) 89 | elems.append(translate(command('2', 90 | '', 91 | 'edit file', 92 | GREEN_3), 93 | x, y+110)) 94 | elems.append(translate(command('3', 95 | 'git commit -m \'fix bug\' file.py', 96 | '', 97 | GREEN_3), 98 | x, y+150)) 99 | elems.append(translate(command('4', 100 | 'git diff fixme master -- file.py', 101 | '', 102 | GREEN_3), 103 | x, y+190)) 104 | 105 | return elems 106 | 107 | 108 | 109 | def branch_section(): 110 | x = 0 111 | y = 0 112 | elems = rev_path(x, y, x+25, y) 113 | # down to right 114 | elems.extend(rev_path(x+25, y, x+75, y+50, "fixme")) 115 | # up to right 116 | elems.extend(rev_path(x+75, y+50, x+125, y, "")) 117 | # across 118 | elems.extend(rev_path(x+25, y, x+125, y, "master")) 119 | 120 | elems.append(translate(revision(), x+25, y)) 121 | elems.append(translate(revision(), x+75, y+50)) 122 | elems.append(translate(revision(), x+125, y)) 123 | 124 | elems.extend(between(x+75, 0, x+75, 50)) 125 | #cap_elems = triangle(-1,1, 0,3, 1,1, RED_3+HALF) 126 | if INKSCAPE_HACK: 127 | cap_elems = triangle(0,1, -2,0, 0,-1, RED_3) 128 | else: 129 | cap_elems = triangle(0,1, 2,0, 0,-1, RED_3) 130 | elems.append(cap_def('RED_3END', cap_elems)) 131 | elems.extend(arc_curve(x+100, 0, x+100, 70, cap_def='RED_3END')) 132 | 133 | # small numbers 134 | elems.append(translate(num('1', GREEN_3), x+15, y+10)) 135 | elems.append(translate(num('2', GREEN_3), x+35, y+30)) 136 | elems.append(translate(num('3', GREEN_3), x+50, y+45)) 137 | 138 | # large numbers 139 | elems.append(translate(command('1', 140 | 'git checkout -t fixme', 141 | 'use -t to set up remote tracking', 142 | GREEN_3), 143 | x, y+70)) 144 | elems.append(translate(command('2', 145 | '', 146 | 'edit file', 147 | GREEN_3), 148 | x, y+110)) 149 | elems.append(translate(command('3', 150 | 'git commit -m \'fix bug\' file.py', 151 | '', 152 | GREEN_3), 153 | x, y+150)) 154 | elems.append(translate(command('4', 155 | 'git diff fixme master -- file.py', 156 | '', 157 | GREEN_3), 158 | x, y+190)) 159 | 160 | return elems 161 | 162 | 163 | def diff(spikes=9, radius=10): 164 | points = [] 165 | sb = ShapeBuilder() 166 | 167 | for i, angle in enumerate(xrange(0, 360+1, float(180)/spikes)): 168 | if i % 2 == 0: 169 | r = radius * .8 170 | else: 171 | r = radius 172 | radians = angle * math.pi/180 173 | coord = '{0},{1}'.format(r*math.cos(radians), 174 | r*math.sin(radians)) 175 | print "ANGLE", angle, "COOR", coord, "RAD", radians 176 | points.append(coord) 177 | print "POINTS", points 178 | line = sb.createPolyline(points=' '.join(points)) 179 | fill = BLACK 180 | stroke = ORANGE_3 181 | style = 'fill:{0};stroke-width:{1};stroke:{2}'.format(fill, 2, stroke) 182 | line.set_style(style) 183 | return [line] 184 | 185 | 186 | def between(x,y, x2,y2, outie=10, angle=45, 187 | color=PUR_3, width=4): 188 | """outie direction is billowing if it were traveling counterclockwise""" 189 | points = [] 190 | sb = ShapeBuilder() 191 | points.append('{0},{1}'.format(x,y)) 192 | slope = slope_angle(x,y, x2,y2) 193 | out_angle = slope + angle 194 | out_r = out_angle* math.pi/180 195 | next_x = x+ outie*math.cos(out_r) 196 | next_y = y - outie*math.sin(out_r) 197 | points.append('{0},{1}'.format(next_x, next_y)) 198 | slope_r = slope*math.pi/180 199 | 200 | distance = math.sqrt((x2-x)**2 + (y2-y)**2) 201 | flat_len = distance - 2 * outie*(math.cos(out_r)) 202 | next_x2 = next_x + flat_len*math.cos(slope_r) 203 | next_y2 = next_y - flat_len*math.sin(slope_r) 204 | 205 | points.append('{0},{1}'.format(next_x2, next_y2)) 206 | points.append('{0},{1}'.format(x2,y2)) 207 | line = sb.createPolyline(points=' '.join(points)) 208 | style = 'fill:{0};stroke-width:{1};stroke:{2};stroke-linecap:round'.format(ALPHA, width, color) 209 | line.set_style(style) 210 | return [line] 211 | 212 | def marker_end_def(name, color, root_elem): 213 | """add root_elem as child of svg/defs/marker@id=name""" 214 | pass 215 | 216 | 217 | def cap_def(name, children): 218 | d = defs() 219 | m = BaseElement('marker') 220 | m.setAttribute('id', name) 221 | m.setAttribute('orient', 'auto') 222 | #m.setAttribute('markerUnits', 'strokeWidth') 223 | d.addElement(m) 224 | for c in children: 225 | m.addElement(c) 226 | return d 227 | 228 | def triangle(x1,y1, x2,y2, x3,y3, fill): 229 | points = [] 230 | sb = ShapeBuilder() 231 | for x, y in [(x1,y1), (x2,y2), (x3,y3), (x1, y1)]: 232 | points.append('{0},{1}'.format(x,y)) 233 | pl = sb.createPolyline(points=' '.join(points)) 234 | style = 'fill:{0}'.format(fill) 235 | pl.set_style(style) 236 | return [pl] 237 | 238 | 239 | def arc_curve(x,y, x2,y2, curve_angle=45, width=10, color=RED_3, 240 | cap_def=None): 241 | """clockwise curve. 242 | Note that you need to create cap_def""" 243 | p = path(pathData='M {0},{1}'.format(x,y)) 244 | # M is move absolute 245 | angle = slope_angle(x,y, x2,y2) 246 | mid_x, mid_y = middle(x,y, x2,y2) 247 | rx = ry = distance(x,y,x2,y2) 248 | #p.appendCubicShorthandCurveToPath(x2,y2) 249 | p.appendArcToPath(rx, ry, x2, y2, relative=False) 250 | style = 'fill:{0};stroke-width:{1};stroke:{2};stroke-linecap:round'.format(ALPHA, width, color) 251 | if cap_def: 252 | p.set_marker_end('url(#{0})'.format(cap_def)) 253 | #style += ';marker-end;url(#{0})'.format(cap_def) 254 | p.set_style(style) 255 | return [p] 256 | 257 | 258 | def revision(): 259 | c = circle(0, 0, 10) 260 | stroke = RED_3 261 | style = 'fill:{0};stroke-width:{1};stroke:{2}'.format(stroke, 5, stroke) 262 | c.set_style(style) 263 | return [c] 264 | 265 | def middle(x1, y1, x2, y2): 266 | return (x1+x2)/2. , (y1+y2)/2. 267 | 268 | def distance(x1, y1, x2, y2): 269 | return math.sqrt((x2-x1)**2 + (y2-y1)**2) 270 | 271 | def slope_angle(x1, y1, x2, y2): 272 | """ 273 | 0 is flat 274 | 45 is pointing down to right 275 | """ 276 | rise = 1.*(y2-y1) 277 | run = x2-x1 278 | slope = None 279 | if run != 0: 280 | slope = rise/run 281 | result = math.atan(slope)*180/math.pi 282 | elif y2 > y1: 283 | result = 270 284 | else: 285 | result = 90 286 | 287 | 288 | print "ROTATE", result, rise, run, slope 289 | return result 290 | 291 | def command(num_txt, cmd, explanation, color): 292 | x = 0 293 | y = 0 294 | elems = [scale(num(num_txt, color), 2)] 295 | cmd_txt = text(cmd, x+40, y+12) 296 | s = StyleBuilder() 297 | s.setFontWeight('bold') 298 | s.setFontFamily('Bitstream Vera Sans Mono') 299 | cmd_txt.set_style(s.getStyle()) 300 | elems.append(cmd_txt) 301 | 302 | exp_txt = text(explanation, x+45, y+27) 303 | s = StyleBuilder() 304 | #s.setFontWeight('bold') 305 | s.setFontFamily('Bitstream Vera Serif') 306 | s.setFontSize('10px') 307 | exp_txt.set_style(s.getStyle()) 308 | elems.append(exp_txt) 309 | return elems 310 | 311 | def num(txt, color): 312 | txt = str(txt) 313 | elems = [] 314 | r = rect(0, 0, 16, 16) 315 | s = StyleBuilder() 316 | s.setFilling(color) 317 | r.set_style(s.getStyle()) 318 | elems.append(r) 319 | if txt: 320 | style2 = StyleBuilder() 321 | #style2.setFontFamily('envy code r') 322 | style2.setFontFamily('arial') 323 | style2.setFontWeight('bold') 324 | style2.setFilling(ALUM_1) 325 | # shift text left and up by a bit 326 | if len(txt) == 1: 327 | x = 5 328 | elif len(txt) == 2: 329 | x = 1.5 330 | t = text(txt, x, 12.5) 331 | t.set_style(style2.getStyle()) 332 | elems.append(t) 333 | return elems 334 | 335 | 336 | def words(txt, family='arial', weight='bold', color=ALUM_1): 337 | style2 = StyleBuilder() 338 | #style2.setFontFamily('envy code r') 339 | style2.setFontFamily('arial') 340 | style2.setFontWeight('bold') 341 | style2.setFilling(ALUM_1) 342 | t = text(txt, 0, 0) 343 | t.set_style(style2.getStyle()) 344 | return t 345 | 346 | def rev_path(x1,y1, x2,y2, txt=None, color=ALUM_6, width=16): 347 | elements = [] 348 | # x1, y1 = r1.get_cx(), r1.get_cy() 349 | # x2, y2 = r2.get_cx(), r2.get_cy(), 350 | l = line(x1, y1, 351 | x2, y2) 352 | elements.append(l) 353 | style = 'stroke-width:{0};stroke:{1}'.format(width, color) 354 | l.set_style(style) 355 | if txt: 356 | x, y = middle(x1, y1, x2, y2) 357 | # shift text left and up by a bit 358 | # whole alphabet in this font is 167 px width 359 | per_char = 167./26 360 | t = translate([words(txt)], -len(txt)/2*per_char, 4) 361 | group = rotate([t], slope_angle(x1, y1, x2, y2)) 362 | group = translate([group], x, y) 363 | elements.append(group) 364 | 365 | 366 | return elements 367 | 368 | def translate(elems, x, y): 369 | group = g() 370 | for e in elems: 371 | group.addElement(e) 372 | tb = TransformBuilder() 373 | #tb.setRotation(rotate(x1, y1, x2, y2)) 374 | tb.setTranslation('{0},{1}'.format(x,y)) 375 | group.set_transform(tb.getTransform()) 376 | return group 377 | 378 | def scale(elems, amount): 379 | """1 = 100%""" 380 | group = g() 381 | for e in elems: 382 | group.addElement(e) 383 | tb = TransformBuilder() 384 | #tb.setRotation(rotate(x1, y1, x2, y2)) 385 | tb.setScaling(amount, amount) 386 | group.set_transform(tb.getTransform()) 387 | return group 388 | 389 | def rotate(elems, angle): 390 | group = g() 391 | for e in elems: 392 | group.addElement(e) 393 | tb = TransformBuilder() 394 | #tb.setRotation(rotate(x1, y1, x2, y2)) 395 | tb.setRotation(angle) 396 | #tb.setTranslation('{0},{1}'.format(x,y)) 397 | group.set_transform(tb.getTransform()) 398 | return group 399 | 400 | def test2(): 401 | fout = svg('test') 402 | r1 = revision() 403 | r2 = revision() 404 | # import pdb; pdb.set_trace() 405 | r2.set_cx(100) 406 | r2.set_cy(200) 407 | elems = rev_path(r1.get_cx(), r1.get_cy(), 408 | r2.get_cx(), r2.get_cy(), 409 | "first") 410 | for e in elems: 411 | fout.addElement(e) 412 | fout.addElement(r1) 413 | fout.addElement(r2) 414 | r3 = revision() 415 | r3.set_cx(200) 416 | r3.set_cy(50) 417 | for e in rev_path(r1.get_cx(), r1.get_cy(), 418 | r3.get_cx(), r3.get_cy(), 419 | "second"): 420 | fout.addElement(e) 421 | fout.addElement(r3) 422 | n = translate(num("4", GREEN_3), 50, 40) 423 | fout.addElement(n) 424 | n = scale([translate(num("11", GREEN_1), 50, 40)], 2) 425 | fout.addElement(n) 426 | fout.save('/tmp/test.svg') 427 | 428 | 429 | def test(): 430 | fout = svg() 431 | fout.addElement(translate(diff(), 120, 60)) 432 | for elem in branch_section2(): 433 | fout.addElement(elem) 434 | #fout.addElement(translate(local_work(), 200, 200)) 435 | fout.save('/tmp/git.svg') 436 | 437 | 438 | class Thing(object): 439 | def __init__(self, items): 440 | if isinstance(items, list): 441 | self.items = items 442 | else: 443 | self.items = [] 444 | self.main_item = self.items[0] 445 | 446 | def getXML(self): 447 | return self.items[0].getXML() 448 | 449 | def __iter__(self): 450 | return iter(self.items) 451 | 452 | def move(self, x, y): 453 | group = g() 454 | for item in self: 455 | group.addElement(item) 456 | tb = TransformBuilder() 457 | tb.setTranslation('{0},{1}'.format(x,y)) 458 | group.set_transform(tb.getTransform()) 459 | self.items = [group] 460 | return self 461 | 462 | def scale(self, amount): 463 | """1 = 100%""" 464 | group = g() 465 | for e in self: 466 | group.addElement(e) 467 | tb = TransformBuilder() 468 | tb.setScaling(amount, amount) 469 | group.set_transform(tb.getTransform()) 470 | self.items = [group] 471 | return self 472 | 473 | def rotate(self, angle): 474 | group = g() 475 | for e in self: 476 | group.addElement(e) 477 | tb = TransformBuilder() 478 | tb.setRotation(angle) 479 | group.set_transform(tb.getTransform()) 480 | self.items = [group] 481 | return self 482 | 483 | 484 | if __name__ == '__main__': 485 | test() 486 | --------------------------------------------------------------------------------