{ "metadata": { "name": "roll_your_own_cms" }, "nbformat": 3, "nbformat_minor": 0, "worksheets": [ { "cells": [ { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "``git`` is not really a \"Version Control System\". It is better described as a \"Content Management System\", that turns out to be really good for version control.\n", "\n", "What should a \"content manager\" do?\n", "\n", "We'll try and design our own, and then see what ``git`` has to say.\n", "\n", "The general idea of this presentation comes from the [git parable](http://tom.preston-werner.com/2009/05/19/the-git-parable.html). The [git foundations](http://matthew-brett.github.com/pydagogue/foundation.html) page extends the same idea." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## A mysterious introduction" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Later on, I'm going to ask you to solve a very difficult problem.\n", "\n", "Here's some background that might help.\n", "\n", "I'm going to describe \"Crytographic hashes\". It won't be obvious why that's a good idea for a little while.\n", "\n", "See : [Wikipedia on hash functions](http://en.wikipedia.org/wiki/Cryptographic_hash_function)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "\n", "\n", "A *hash* is the result of running a *hash function* over a block of data. The hash is a fixed length string that is the *signature* of that exact block of data. Let's run this in Python:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import hashlib\n", "sha1_hash_function = hashlib.sha1\n", "sha1_hash_function" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 1, "text": [ "" ] } ], "prompt_number": 1 }, { "cell_type": "code", "collapsed": false, "input": [ "message = \"git is a rude word in UK English\"\n", "hash_value = sha1_hash_function(message).hexdigest()\n", "hash_value" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 2, "text": [ "'fec41478c4f497c1d90fd28610f4272c78a6867e'" ] } ], "prompt_number": 2 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Not too exciting so far. However, the rather magical nature of this string is not yet apparent. Here's the trick:\n", "\n", "**There is no practical way for you to find another `message` that will give the same `hash_value`**\n", "\n", "The `hash_value` then is (nearly) completely unique to that set of bytes.\n", "\n", "For example, a tiny change in the string makes the hash completely different:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "sha1_hash_function(\"git is a rude word in UK English.\").hexdigest() # Add a full stop" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 3, "text": [ "'9e87add001f13aa79ed7b42a5effbfc60aa8584e'" ] } ], "prompt_number": 3 }, { "cell_type": "markdown", "metadata": {}, "source": [ "So, if you give me some data, and I calculate the hash value, and it comes out as \"fec41478c4f497c1d90fd28610f4272c78a6867e\", then I can be very sure that the data you gave me was exactly the string \"git is a rude word in UK English\"." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## The story so far..." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You are writing a breakthrough paper showing that you can entirely explain the brain using random numbers. You've got the draft paper, and the analysis script and a figure for the paper. These are all in a directory modestly named ``nobel_prize``.\n", "\n", "In this directory, you have the paper draft ``nobel_prize.txt``, the analysis script ``very_clever_analysis.py``, and a figure for the paper ``stunning_figure.png``." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Make the directory for the paper and change into the directory\n", "from os import chdir, getcwd, mkdir, listdir\n", "from os.path import split as psplit, abspath, isdir\n", "import shutil\n", "# If we are in the nobel_prize directory already, get out\n", "current_path = abspath(getcwd())\n", "if current_path.endswith('nobel_prize'):\n", " chdir('..') # Change down a directory\n", "# If the directory exists, nuke it\n", "if isdir('nobel_prize'):\n", " shutil.rmtree('nobel_prize')\n", "# Make the new directory, and change to it\n", "mkdir('nobel_prize')\n", "chdir('nobel_prize')\n", "# Check we are there\n", "print(getcwd())\n", "# The directory should be empty at this point\n", "assert listdir('.') == []" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "/Users/mb312/dev_trees/pna-notebooks/nobel_prize\n" ] } ], "prompt_number": 4 }, { "cell_type": "code", "collapsed": false, "input": [ "%%file nobel_prize.txt\n", "The brain is just a set of random numbers\n", "=========================================\n", "\n", "We have discovered that the brain is a set of random numbers.\n", "\n", "We have charts and graphs to back us up.\n" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing nobel_prize.txt\n" ] } ], "prompt_number": 5 }, { "cell_type": "code", "collapsed": false, "input": [ "%%file very_clever_analysis.py\n", "# The brain analysis script\n", "import numpy as np\n", "\n", "# Make brain data\n", "brain_size = (128, 128)\n", "random_data = np.random.normal(size=brain_size)\n" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing very_clever_analysis.py\n" ] } ], "prompt_number": 6 }, { "cell_type": "code", "collapsed": false, "input": [ "# Get the figure from storage\n", "!cp ../images/stunning_figure.png ." ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "prompt_number": 7 }, { "cell_type": "code", "collapsed": false, "input": [ "import os\n", "os.listdir('.')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 8, "text": [ "['nobel_prize.txt', 'stunning_figure.png', 'very_clever_analysis.py']" ] } ], "prompt_number": 8 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The dog ate my results" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You've been working on this paper for a while.\n", "\n", "About 2 weeks ago, you were very excited with the results. You ran the script, made the figure, and went to your advisor, Josephine. She was excited too. The figure looks good! You get ready to publish in Science.\n", "\n", "Today you finished cleaning up for the Science paper, and reran the analysis, and it doesn't look that good anymore. You go to see Josephine. She says \"It used to look better than that\". That's what you think too. But:\n", "\n", "* **Did it really look better before?**\n", "* If it did, **why does it look different now?**" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Deja vu all over again" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Given you are so clever and you have worked out the brain, it's really easy for you to leap in your time machine, and go back two weeks to start again.\n", "\n", "What are you going to do differently this time?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Make regular snapshots" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "You decide to make your own content management system called ``fancy_backups``.\n", "\n", "It's very simple.\n", "\n", "Every time you finish doing some work on your paper, you make a snapshot of the analysis directory.\n", "\n", "The snapshot is just a copy of all the files in the directory, kept somewhere safe.\n", "\n", "You make a directory to store the snapshots called ``.fancy_backups``:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!mkdir .fancy_backups" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 9 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Then you make a directory for the first backup:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!mkdir .fancy_backups/1\n", "!mkdir .fancy_backups/1/files" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 10 }, { "cell_type": "markdown", "metadata": {}, "source": [ "And you copy the files there:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cp * .fancy_backups/1/files" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 11 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "skip" } }, "source": [ "Some utility code to show directory listings" ] }, { "cell_type": "code", "collapsed": false, "input": [ "\"\"\" A little utility to display the structure of directory trees\n", "\n", "Don't worry about the detail here, you'll see below what it does.\n", "The code is not complicated, but it's not relevant to the main points on git.\n", "\n", "Inspired by:\n", "\n", "http://lorenzod8n.wordpress.com/2007/12/13/creating-a-tree-utility-in-python-part-2/\n", "\n", "with thanks.\n", "\n", "This version is my own copyright (Matthew Brett) released under 2-clause BSD\n", "\"\"\"\n", "from os import getcwd, listdir\n", "from os.path import isdir, join as pjoin\n", "\n", "# Unicode constants for constructing the tree trunk and branches\n", "ALONG = u'\\u2500'\n", "DOWN = u'\\u2502'\n", "DOWN_RIGHT = u'\\u251c'\n", "ELBOW_RIGHT = u'\\u2514'\n", "BLUE = u'\\033[94m'\n", "ENDC = u'\\033[0m'\n", "DOWN_RIGHT_ALONG = DOWN_RIGHT + ALONG * 2 + u\" \"\n", "ELBOW_RIGHT_ALONG = ELBOW_RIGHT + ALONG * 2 + u\" \"\n", "CONTINUE_INDENT = DOWN + u' ' * 3\n", "FINISH_INDENT = u' ' * 4\n", "\n", "\n", "def print_tree(root_path=None, indent_str=u''):\n", " \"\"\" Print tree structure starting from `root_path`\n", "\n", " Parameters\n", " ----------\n", " root_path : None or str, optional\n", " path from which to print directory tree structure. If None, use current\n", " directory.\n", " indent_str : str, optional\n", " prefix to print for every entry in the tree. Usually '', and then set\n", " by recursion into the function when printing subdirectories.\n", "\n", " Returns\n", " -------\n", " None\n", " \"\"\"\n", " if root_path is None:\n", " root_path = getcwd()\n", " # ensure return of unicode paths from listdir\n", " root_path = unicode(root_path)\n", " paths = sorted(listdir(root_path))\n", " if len(paths) == 0:\n", " return\n", " for path in paths[:-1]:\n", " print_path(root_path, path, indent_str, False)\n", " print_path(root_path, paths[-1], indent_str, True)\n", "\n", "\n", "def print_path(root_path, path, indent_str, last_entry=False):\n", " \"\"\" Print individual `path` (file or directory name) from `root_path`\n", "\n", " Parameters\n", " ----------\n", " root_path : str\n", " path containing `path`\n", " path : str\n", " file name or directory name\n", " indent_str : str\n", " string to prefix to entry for this `path`\n", " last_entry : bool, optional\n", " Whether this is the last entry in a list of paths.\n", "\n", " Returns\n", " -------\n", " None\n", " \"\"\"\n", " full_path = pjoin(root_path, path)\n", " have_dir = isdir(full_path)\n", " leader = ELBOW_RIGHT_ALONG if last_entry else DOWN_RIGHT_ALONG\n", " path_colored = BLUE + path + ENDC if have_dir else path\n", " print(indent_str + leader + path_colored)\n", " if have_dir:\n", " new_indent = FINISH_INDENT if last_entry else CONTINUE_INDENT\n", " print_tree(full_path, indent_str + new_indent)" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "prompt_number": 12 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Reminding yourself of what you did" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "For good measure, you put a file in the snapshot directory to remind you when you did the snapshot, and who did it, and what was new for this snapshot" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/1/info.txt\n", "Date: April 1 2012, 14.30\n", "Author: I. M. Awesome\n", "Notes: First backup of my amazing idea" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/1/info.txt" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 13 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now you have these files in the ``nobel_prize`` directory:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree()" ], "language": "python", "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94m.fancy_backups\u001b[0m\n", "\u2502 \u2514\u2500\u2500 \u001b[94m1\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 nobel_prize.txt\n", "\u251c\u2500\u2500 stunning_figure.png\n", "\u2514\u2500\u2500 very_clever_analysis.py\n" ] } ], "prompt_number": 14 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Every time you do some work on the files, you back them up in the same way. After a few days:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!echo \"The charts are very impressive\\n\" >> nobel_prize.txt\n", "!mkdir .fancy_backups/2\n", "!mkdir .fancy_backups/2/files\n", "!cp * .fancy_backups/2/files" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "prompt_number": 15 }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/2/info.txt\n", "Date: April 1 2012, 18.03\n", "Author: I. M. Awesome\n", "Notes: Fruit of enormous thought" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/2/info.txt" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 16 }, { "cell_type": "code", "collapsed": false, "input": [ "!echo \"The graphs are also compelling\\n\" >> nobel_prize.txt\n", "!mkdir .fancy_backups/3\n", "!mkdir .fancy_backups/3/files\n", "!cp * .fancy_backups/3/files" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "prompt_number": 17 }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/3/info.txt\n", "Date: April 2 2012, 11.20\n", "Author: I. M. Awesome\n", "Notes: Now seeing things clearly" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/3/info.txt" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 18 }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree()" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94m.fancy_backups\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94m1\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2502 \u2514\u2500\u2500 info.txt\n", "\u2502 \u251c\u2500\u2500 \u001b[94m2\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2502 \u2514\u2500\u2500 info.txt\n", "\u2502 \u2514\u2500\u2500 \u001b[94m3\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 nobel_prize.txt\n", "\u251c\u2500\u2500 stunning_figure.png\n", "\u2514\u2500\u2500 very_clever_analysis.py\n" ] } ], "prompt_number": 19 }, { "cell_type": "code", "collapsed": false, "input": [ "# Now we just cheat, and copy the commits\n", "!cp -r .fancy_backups/3 .fancy_backups/4\n", "!cp -r .fancy_backups/3 .fancy_backups/5\n", "!cp -r .fancy_backups/3 .fancy_backups/6" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "prompt_number": 20 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "You keep doing this, until again the day comes to talk to Josephine. You now have 5 backups.\n", "\n", "The future has not changed. Josephine again thinks the results have changed. But now - you can check.\n", "\n", "You go back and look at `.fancy_backups/1/stunning_figure.png`. It does look different.\n", "\n", "You go through all the `.fancy_backups` directories in order. It turns out that the figure changes in `.fancy_backups/4`.\n", "\n", "You look in `.fancy_backups/4/info.txt` and it says:\n", " \n", " Date: April 8 2012, 01.40\n", " Author: I. M. Awesome\n", " Notes: I always get the best ideas after I have been drinking.\n", " \n", "Aha. Then you go find the problem in `fancy_backups/4/very_clever_analysis.py`.\n", " \n", "You fix `very_clever_analysis.py`.\n", "\n", "You make a new snapshot `.fancy_backups/6`.\n", "\n", "Back on track for a scientific breakthrough" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Terminology breakout" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are some [terms](https://www.kernel.org/pub/software/scm/git/docs/gitglossary.html).\n", "\n", "* Working tree: the files you are working on in the current directory (`nobel_prize`). The files `very_clever_analysis.py`, `nobel_prize.txt`, `stunning_figure.png` are the files in your working tree\n", "* Repository: the directory containing information about the history of the files. Your directory `.fancy_snapshot` is the repository.\n", "* Commit: a completed snapshot. For example, `.fancy_backups/1` contains one commit.\n", " \n", "We'll use these terms to get used to them." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Breaking up work into chunks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "You did some edits to the paper `nobel_prize.txt` to edit the introduction.\n", "\n", "You also had a good idea for the analysis, and did some edits to `very_clever_analysis.py`.\n", "\n", "You've got used to breaking each new *commit* (snapshot) up into little bits of extra work, with their own comments in the `info.txt`.\n", "\n", "You want to make two commits from your changes:\n", "\n", "1. a commit containing the changes to `nobel_prize.txt`, with comment \"Changes to introduction\"\n", "2. a commit containing the changes to `very_clever_analysis.py` with comment \"Crazy new analysis\"\n", "\n", "How can I do that?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The staging area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We adapt the workflow. Each time we start work, after a commit, we copy the last commit to directory `.fancy_backups/staging_area`. That will the default contents of our next commit." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!mkdir .fancy_backups/staging_area\n", "!cp .fancy_backups/6/files/* .fancy_backups/staging_area\n", "!ls .fancy_backups/staging_area" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "nobel_prize.txt stunning_figure.png very_clever_analysis.py\r\n" ] } ], "prompt_number": 21 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now, you do your edits to `nobel_prize.txt`, and `very_clever_analysis.py` in your *working tree* (the `nobel_prize` directory). Then you get ready for the next commit with:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cp nobel_prize.txt .fancy_backups/staging_area" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 22 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The staging area now contains all the files for the upcoming commit (snapshot).\n", "\n", "You make the commit with:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!mkdir .fancy_backups/7\n", "!mkdir .fancy_backups/7/files\n", "!cp .fancy_backups/staging_area/* .fancy_backups/7/files" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 23 }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/7/info.txt\n", "Date: April 10 2012, 14.30\n", "Author: I. M. Awesome\n", "Notes: Changes to introduction" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/7/info.txt" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 24 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Now you are ready for the next commit, with the changes to the analysis script." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cp very_clever_analysis.py .fancy_backups/staging_area" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 25 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The commit is now *staged* and ready to be saved." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Our commit procedure; same as last time with \"8\" instead of \"7\"\n", "!mkdir .fancy_backups/8\n", "!mkdir .fancy_backups/8/files\n", "!cp .fancy_backups/staging_area/* .fancy_backups/8/files" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 26 }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/8/info.txt\n", "Date: April 10 2012, 14.35\n", "Author: I. M. Awesome\n", "Notes: Crazy new analysis" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/8/info.txt" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 27 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Here is what we have in our `.fancy_backups` directory:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree('.fancy_backups')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94m1\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m2\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m3\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m4\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m5\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m6\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m7\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u251c\u2500\u2500 \u001b[94m8\u001b[0m\n", "\u2502 \u251c\u2500\u2500 \u001b[94mfiles\u001b[0m\n", "\u2502 \u2502 \u251c\u2500\u2500 nobel_prize.txt\n", "\u2502 \u2502 \u251c\u2500\u2500 stunning_figure.png\n", "\u2502 \u2502 \u2514\u2500\u2500 very_clever_analysis.py\n", "\u2502 \u2514\u2500\u2500 info.txt\n", "\u2514\u2500\u2500 \u001b[94mstaging_area\u001b[0m\n", " \u251c\u2500\u2500 nobel_prize.txt\n", " \u251c\u2500\u2500 stunning_figure.png\n", " \u2514\u2500\u2500 very_clever_analysis.py\n" ] } ], "prompt_number": 28 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Now the very difficult problem" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's say that the figure `stunning_figure.png` is large.\n", "\n", "Let's say it changes only once across our 8 commits, at commit 5.\n", "\n", "What should we do to save disk space for `.fancy_backups`?\n" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "### Exercise - your strategy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Split into pairs.\n", "\n", "In five minutes, we'll share ideas." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### My crazy idea - use hash values as file names" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Make a new directory:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!mkdir .fancy_backups/objects" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 29 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Calculate the hash value for the figure:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "import hashlib\n", "# read the bytes for the figure into a string\n", "figure_data = open('.fancy_backups/1/files/stunning_figure.png', 'rb').read()\n", "# Calculate the hash value\n", "figure_hash = hashlib.sha1(figure_data).hexdigest()\n", "figure_hash" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 30, "text": [ "'aff88ecead2c7166770969a54dc855c8b91be864'" ] } ], "prompt_number": 30 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Save the figure with the hash value as the file name:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cp .fancy_backups/1/files/stunning_figure.png .fancy_backups/objects/$figure_hash" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 31 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Do the same for the other two files in the first commit." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import os\n", "\n", "def copy_to_hash(fname):\n", " # Read the data\n", " binary_data = open(fname, 'rb').read()\n", " # Find the hash\n", " hash_value = hashlib.sha1(binary_data).hexdigest()\n", " # Save with the hash filename\n", " hash_fname = '.fancy_backups/objects/' + hash_value\n", " if os.path.isfile(hash_fname):\n", " print \"We already have a hash file for \" + fname\n", " else:\n", " open(hash_fname, 'wb').write(binary_data)\n", " return hash_value" ], "language": "python", "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [], "prompt_number": 32 }, { "cell_type": "code", "collapsed": false, "input": [ "paper_hash = copy_to_hash('.fancy_backups/1/files/nobel_prize.txt')\n", "script_hash = copy_to_hash('.fancy_backups/1/files/very_clever_analysis.py')" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 33 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Making the directory listing" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here are the hashes for all files:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print 'Hash for stunning_figure.png:', figure_hash\n", "print 'Hash for nobel_prize:', paper_hash\n", "print 'Hash for very_clever_analysis.py:', script_hash" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Hash for stunning_figure.png:" ] }, { "output_type": "stream", "stream": "stdout", "text": [ " aff88ecead2c7166770969a54dc855c8b91be864\n", "Hash for nobel_prize: 3af8809ecb9c6dec33fc7e5ad330e384663f5a0d\n", "Hash for very_clever_analysis.py: e7f3ca9157fd7088b6a927a618e18d4bc4712fb6\n" ] } ], "prompt_number": 34 }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree('.fancy_backups/objects')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 3af8809ecb9c6dec33fc7e5ad330e384663f5a0d\n", "\u251c\u2500\u2500 aff88ecead2c7166770969a54dc855c8b91be864\n", "\u2514\u2500\u2500 e7f3ca9157fd7088b6a927a618e18d4bc4712fb6\n" ] } ], "prompt_number": 35 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now point the snapshot to the hash filename versions of the files by making a *text directory listing* or *tree* listing:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/1/directory_list\n", "Filename Hash value\n", "======== ==========\n", "stunning_figure.png aff88ecead2c7166770969a54dc855c8b91be864\n", "nobel_prize.txt 3af8809ecb9c6dec33fc7e5ad330e384663f5a0d\n", "very_clever_analysis.py e7f3ca9157fd7088b6a927a618e18d4bc4712fb6" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/1/directory_list\n" ] } ], "prompt_number": 36 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The next commit - saves space!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Nothing to for unchanged figure - already have hash value file\n", "figure_hash = copy_to_hash('.fancy_backups/2/files/stunning_figure.png')\n", "# Makes a new copy\n", "paper_hash = copy_to_hash('.fancy_backups/2/files/nobel_prize.txt')\n", "# Nothing to for unchanged script - already have hash value file\n", "script_hash = copy_to_hash('.fancy_backups/2/files/very_clever_analysis.py')\n", "print 'Hash for stunning_figure.png:', figure_hash\n", "print 'Hash for nobel_prize:', paper_hash\n", "print 'Hash for very_clever_analysis.py:', script_hash" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "We already have a hash file for .fancy_backups/2/files/stunning_figure.png\n", "We already have a hash file for .fancy_backups/2/files/very_clever_analysis.py\n", "Hash for stunning_figure.png: aff88ecead2c7166770969a54dc855c8b91be864\n", "Hash for nobel_prize: bc330d8886bb1b36a49d8e7ebb07d3443190b0e6\n", "Hash for very_clever_analysis.py: e7f3ca9157fd7088b6a927a618e18d4bc4712fb6\n" ] } ], "prompt_number": 37 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Only the `nobel_prize.txt` has changed. So:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/2/directory_list\n", "Filename Hash value\n", "======== ==========\n", "stunning_figure.png aff88ecead2c7166770969a54dc855c8b91be864\n", "nobel_prize.txt bc330d8886bb1b36a49d8e7ebb07d3443190b0e6\n", "very_clever_analysis.py e7f3ca9157fd7088b6a927a618e18d4bc4712fb6" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/2/directory_list\n" ] } ], "prompt_number": 38 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### An even more crazy idea - hash the tree" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "I guess we can store the tree for the first commit according to its hash:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "tree_hash = copy_to_hash('.fancy_backups/1/directory_list')\n", "tree_hash" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 39, "text": [ "'63d466086dd359c0b34d21e04b812781c7153b23'" ] } ], "prompt_number": 39 }, { "cell_type": "markdown", "metadata": {}, "source": [ "And we can do the same for the second commit:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "copy_to_hash('.fancy_backups/2/directory_list')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 40, "text": [ "'d45095659d1ea567b90aaf923e265bf41cbb126f'" ] } ], "prompt_number": 40 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now - would I get the same hash if I had had a different figure? " ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Even more crazy idea - make the whole commit into a text file" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "How about this?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/1/commit\n", "Date: April 1 2012, 14.30\n", "Author: I. M. Awesome\n", "Notes: First backup of my amazing idea\n", "Tree: 63d466086dd359c0b34d21e04b812781c7153b23" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/1/commit\n" ] } ], "prompt_number": 41 }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now I can hash the commit!" ] }, { "cell_type": "code", "collapsed": false, "input": [ "commit_hash = copy_to_hash('.fancy_backups/1/commit')\n", "commit_hash" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "pyout", "prompt_number": 42, "text": [ "'f473e51a38d40772722205c92dddd4bdfd941ef8'" ] } ], "prompt_number": 42 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Would the commit hash value change if the figure changed?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### So crazy it's actually git" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now look in `.fancy_backups/objects`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "ls .fancy_backups/objects" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "3af8809ecb9c6dec33fc7e5ad330e384663f5a0d bc330d8886bb1b36a49d8e7ebb07d3443190b0e6 f473e51a38d40772722205c92dddd4bdfd941ef8\r\n", "63d466086dd359c0b34d21e04b812781c7153b23 d45095659d1ea567b90aaf923e265bf41cbb126f\r\n", "aff88ecead2c7166770969a54dc855c8b91be864 e7f3ca9157fd7088b6a927a618e18d4bc4712fb6\r\n" ] } ], "prompt_number": 43 }, { "cell_type": "markdown", "metadata": {}, "source": [ "That's 3 file copies for the files from the first commit, 1 file copy from the second commit, 2 directory listings (for first and second commit) and one commit listing (for the first commit) = 7 hash objects." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We know that the file beginning 'f473e51' *completely defines the first commit*:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .fancy_backups/objects/f473e51a38d40772722205c92dddd4bdfd941ef8" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Date: April 1 2012, 14.30\r\n", "Author: I. M. Awesome\r\n", "Notes: First backup of my amazing idea\r\n", "Tree: 63d466086dd359c0b34d21e04b812781c7153b23" ] } ], "prompt_number": 44 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Linking the commits" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Can we completely get rid of `.fancy_backups/1`, `.fancy_backups/2` ?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The reason for our commit names \"1\",\"2\", \"3\" was so we know that commit \"2\" comes after commit \"1\" and before commit \"3\". Now our commits have arbitrary hashes, we can't tell the order from the name." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We can specify the order by adding the commit hash of the previous commit into the current commit:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file .fancy_backups/temporary_file\n", "Date: April 1 2012, 18.03\n", "Author: I. M. Awesome\n", "Notes: Fruit of enormous thought\n", "Tree: d45095659d1ea567b90aaf923e265bf41cbb126f\n", "Parent: 63dab6e48ed2e9111624a3b5391bdf784c040b86" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Writing .fancy_backups/temporary_file\n" ] } ], "prompt_number": 45 }, { "cell_type": "code", "collapsed": false, "input": [ "commit2_hash = copy_to_hash('.fancy_backups/temporary_file')" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 46 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Now we have the order of the commits from the links between them." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now you are already a git master." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Gitting going (sorry)" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "**Note**: Much of the rest of this presentation comes from Fernando Perez' excellent git tutorial in his [reproducible software repository](https://github.com/fperez/reprosw). Thanks to Fernando for sharing." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We need to tell git about us before we start. This stuff will go into the commit information." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git config --global user.name \"Matthew Brett\"\n", "!git config --global user.email \"matthew.brett@gmail.com\"" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 47 }, { "cell_type": "markdown", "metadata": {}, "source": [ "git often needs to call up a text editor. Choose the editor you like here:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# Put here your preferred editor. \n", "!git config --global core.editor gedit" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 48 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We also turn on the use of color, which is very helpful in making the output of git easier to read" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git config --global color.ui \"auto\"" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 49 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Getting help" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "usage: git [--version] [--exec-path[=]] [--html-path] [--man-path] [--info-path]\r\n", " [-p|--paginate|--no-pager] [--no-replace-objects] [--bare]\r\n", " [--git-dir=] [--work-tree=] [--namespace=]\r\n", " [-c name=value] [--help]\r\n", " []\r\n", "\r\n", "The most commonly used git commands are:\r\n", " add Add file contents to the index\r\n", " bisect Find by binary search the change that introduced a bug\r\n", " branch List, create, or delete branches\r\n", " checkout Checkout a branch or paths to the working tree\r\n", " clone Clone a repository into a new directory\r\n", " commit Record changes to the repository\r\n", " diff Show changes between commits, commit and working tree, etc\r\n", " fetch Download objects and refs from another repository\r\n", " grep Print lines matching a pattern\r\n", " init Create an empty git repository or reinitialize an existing one\r\n", " log Show commit logs\r\n", " merge Join two or more development histories together\r\n", " mv Move or rename a file, a directory, or a symlink\r\n", " pull Fetch from and merge with another repository or a local branch\r\n", " push Update remote refs along with associated objects\r\n", " rebase Forward-port local commits to the updated upstream head\r\n", " reset Reset current HEAD to the specified state\r\n", " rm Remove files from the working tree and from the index\r\n", " show Show various types of objects\r\n", " status Show the working tree status\r\n", " tag Create, list, delete or verify a tag object signed with GPG\r\n", "\r\n", "See 'git help ' for more information on a specific command.\r\n" ] } ], "prompt_number": 50 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Try `git help add` for an example." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Initializing the repository" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "-" } }, "source": [ "Let's make this `nobel_prize` directory be version controlled with git." ] }, { "cell_type": "code", "collapsed": false, "input": [ "# First pull the contents of the first commit from .fancy_backups\n", "!cp .fancy_backups/1/files/* .\n", "# Then delete .fancy_backups - we're done with those guys\n", "!rm -rf .fancy_backups" ], "language": "python", "metadata": { "slideshow": { "slide_type": "skip" } }, "outputs": [], "prompt_number": 51 }, { "cell_type": "code", "collapsed": false, "input": [ "!ls -a" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u001b[34m.\u001b[m\u001b[m \u001b[34m..\u001b[m\u001b[m nobel_prize.txt stunning_figure.png very_clever_analysis.py\r\n" ] } ], "prompt_number": 52 }, { "cell_type": "code", "collapsed": false, "input": [ "!git init" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Initialized empty Git repository in /Users/mb312/dev_trees/pna-notebooks/nobel_prize/.git/\r\n" ] } ], "prompt_number": 53 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Just what we were expecting; a *repository* directory called ``.git``" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!ls .git" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "HEAD \u001b[34mbranches\u001b[m\u001b[m config \u001b[31mdescription\u001b[m\u001b[m \u001b[34mhooks\u001b[m\u001b[m \u001b[34minfo\u001b[m\u001b[m \u001b[34mobjects\u001b[m\u001b[m \u001b[34mrefs\u001b[m\u001b[m\r\n" ] } ], "prompt_number": 54 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `objects` directory looks familiar. What's in there?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree('.git/objects')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94minfo\u001b[0m" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "\u2514\u2500\u2500 \u001b[94mpack\u001b[0m\n" ] } ], "prompt_number": 55 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Nothing. That makes sense." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git add - put stuff into the staging area" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git add nobel_prize.txt" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 56 }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree('.git/objects')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94md9\u001b[0m" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "\u2502 \u2514\u2500\u2500 2d079af6a7f276cc8d63dcf2549c03e7deb553\n", "\u251c\u2500\u2500 \u001b[94minfo\u001b[0m\n", "\u2514\u2500\u2500 \u001b[94mpack\u001b[0m\n" ] } ], "prompt_number": 57 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Doing `git add nobel_prize.txt` has added a file to the `.git/objects` directory. That filename looks suspiciously like a hash." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git add and the staging area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We expect that `git add` added the file to the *staging area*. Have we got any evidence of that?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "#\r\n", "# Initial commit\r\n", "#\r\n", "# Changes to be committed:\r\n", "# (use \"git rm --cached ...\" to unstage)\r\n", "#\r\n", "#\t\u001b[32mnew file: nobel_prize.txt\u001b[m\r\n", "#\r\n", "# Untracked files:\r\n", "# (use \"git add ...\" to include in what will be committed)\r\n", "#\r\n", "#\t\u001b[31mstunning_figure.png\u001b[m\r\n", "#\t\u001b[31mvery_clever_analysis.py\u001b[m\r\n" ] } ], "prompt_number": 58 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Reading real git objects" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Git objects are nearly as simple as the objects we were writing in `.fancy_backups`.\n", "\n", "The main difference is that, to save space, they are compressed, in fact using a library called `zlib`.\n", "\n", "Here's a routine that gives us the raw contents of stuff in the `.git/objects` directory. There are neater commands in git to do this, but this shows just how simple these objects are." ] }, { "cell_type": "code", "collapsed": false, "input": [ "import zlib # A compression / decompression library\n", "\n", "def read_git_object(filename):\n", " \"\"\" Read an object in the ``.git/objects`` directory\n", "\n", " Yes, it's this simple - in this case. But git reserves the right to use\n", " more complicated storage formats to save space when your repository is larger\n", " \"\"\"\n", " compressed_contents = open(filename, 'rb').read()\n", " return zlib.decompress(compressed_contents)" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 59 }, { "cell_type": "code", "collapsed": false, "input": [ "filename = '.git/objects/d9/2d079af6a7f276cc8d63dcf2549c03e7deb553'\n", "new_object_data = read_git_object(filename)\n", "print new_object_data" ], "language": "python", "metadata": { "slideshow": { "slide_type": "slide" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "blob 189\u0000The brain is just a set of random numbers\n", "=========================================\n", "\n", "We have discovered that the brain is a set of random numbers.\n", "\n", "We have charts and graphs to back us up.\n", "\n" ] } ], "prompt_number": 60 }, { "cell_type": "markdown", "metadata": {}, "source": [ "It's just the contents of the file, with some cruft at the start (`blob 189`)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Staging the other files" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git add stunning_figure.png\n", "!git add very_clever_analysis.py" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 61 }, { "cell_type": "code", "collapsed": false, "input": [ "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "#\r\n", "# Initial commit\r\n", "#\r\n", "# Changes to be committed:\r\n", "# (use \"git rm --cached ...\" to unstage)\r\n", "#\r\n", "#\t\u001b[32mnew file: nobel_prize.txt\u001b[m\r\n", "#\t\u001b[32mnew file: stunning_figure.png\u001b[m\r\n", "#\t\u001b[32mnew file: very_clever_analysis.py\u001b[m\r\n", "#\r\n" ] } ], "prompt_number": 62 }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree('.git/objects')" ], "language": "python", "metadata": { "slideshow": { "slide_type": "subslide" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94m8a\u001b[0m" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n", "\u2502 \u2514\u2500\u2500 dc8bf371d669242ea998f78f9916867cc6203c\n", "\u251c\u2500\u2500 \u001b[94m9f\u001b[0m\n", "\u2502 \u2514\u2500\u2500 fc311b29f0c87bf502b92df951d90ef88c1c7d\n", "\u251c\u2500\u2500 \u001b[94md9\u001b[0m\n", "\u2502 \u2514\u2500\u2500 2d079af6a7f276cc8d63dcf2549c03e7deb553\n", "\u251c\u2500\u2500 \u001b[94minfo\u001b[0m\n", "\u2514\u2500\u2500 \u001b[94mpack\u001b[0m\n" ] } ], "prompt_number": 63 }, { "cell_type": "code", "collapsed": false, "input": [ "print read_git_object('.git/objects/8a/dc8bf371d669242ea998f78f9916867cc6203c')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "blob 138\u0000# The brain analysis script\n", "import numpy as np\n", "\n", "# Make brain data\n", "brain_size = (128, 128)\n", "random_data = np.random.normal(size=brain_size)\n", "\n" ] } ], "prompt_number": 64 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git commit - making the snapshot" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git commit -m \"First backup of my amazing idea\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[master (root-commit) 1d7415e] First backup of my amazing idea\r\n", " 3 files changed, 12 insertions(+)\r\n", " create mode 100644 nobel_prize.txt\r\n", " create mode 100644 stunning_figure.png\r\n", " create mode 100644 very_clever_analysis.py\r\n" ] } ], "prompt_number": 65 }, { "cell_type": "markdown", "metadata": {}, "source": [ "In the commit above, we used the -m flag to specify a message at the command line. If we don't do that, git will open the editor we specified in our configuration above and require that we enter a message. By default, git refuses to record changes that don't have a message to go along with them (though you can obviously 'cheat' by using an empty or meaningless string: git only tries to facilitate best practices, it's not your nanny)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "subslide" } }, "source": [ "We are now expecting to have a .git object for the directory tree, and for the commit." ] }, { "cell_type": "code", "collapsed": false, "input": [ "print_tree('.git/objects')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u251c\u2500\u2500 \u001b[94m1d\u001b[0m\n", "\u2502 \u2514\u2500\u2500 7415e14ef0d556a97630997644307942f7f703\n", "\u251c\u2500\u2500 \u001b[94m1e\u001b[0m\n", "\u2502 \u2514\u2500\u2500 322f4e782df4dfe963eda96517886f4e39a454\n", "\u251c\u2500\u2500 \u001b[94m8a\u001b[0m\n", "\u2502 \u2514\u2500\u2500 dc8bf371d669242ea998f78f9916867cc6203c\n", "\u251c\u2500\u2500 \u001b[94m9f\u001b[0m\n", "\u2502 \u2514\u2500\u2500 fc311b29f0c87bf502b92df951d90ef88c1c7d\n", "\u251c\u2500\u2500 \u001b[94md9\u001b[0m\n", "\u2502 \u2514\u2500\u2500 2d079af6a7f276cc8d63dcf2549c03e7deb553\n", "\u251c\u2500\u2500 \u001b[94minfo\u001b[0m\n", "\u2514\u2500\u2500 \u001b[94mpack\u001b[0m\n" ] } ], "prompt_number": 66 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Here's the tree:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "print read_git_object('.git/objects/1e/322f4e782df4dfe963eda96517886f4e39a454')" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "tree 141\u0000100644 nobel_prize.txt\u0000\ufffd-\u0007\ufffd\ufffd\ufffd\ufffdv\u030dc\ufffd\ufffdT\ufffd\u0003\ufffd\u07b5S100644 stunning_figure.png\u0000\ufffd\ufffd1\u001b)\ufffd\ufffd{\ufffd\u0002\ufffd-\ufffdQ\ufffd\u000e\ufffd\ufffd\u001c", "}100644 very_clever_analysis.py\u0000\ufffd\u070b\ufffdq\ufffdi$.\ufffd\ufffd\ufffd\ufffd\ufffd\u0016\ufffd|\ufffd <\n" ] } ], "prompt_number": 67 }, { "cell_type": "markdown", "metadata": {}, "source": [ "These are in fact the file permissions, the filenames, and a binary form of the file hashes. Git will read this in a more friendly form for us:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git ls-tree 1e322f4" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "100644 blob d92d079af6a7f276cc8d63dcf2549c03e7deb553\tnobel_prize.txt\r\n", "100644 blob 9ffc311b29f0c87bf502b92df951d90ef88c1c7d\tstunning_figure.png\r\n", "100644 blob 8adc8bf371d669242ea998f78f9916867cc6203c\tvery_clever_analysis.py\r\n" ] } ], "prompt_number": 68 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git log - what are the commits so far?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git log" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u001b[33mcommit 1d7415e14ef0d556a97630997644307942f7f703\u001b[m\r\n", "Author: Matthew Brett \r\n", "Date: Thu May 2 22:04:55 2013 -0700\r\n", "\r\n", " First backup of my amazing idea\r\n" ] } ], "prompt_number": 69 }, { "cell_type": "code", "collapsed": false, "input": [ "!git log --parents" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u001b[33mcommit 1d7415e14ef0d556a97630997644307942f7f703\u001b[m\r\n", "Author: Matthew Brett \r\n", "Date: Thu May 2 22:04:55 2013 -0700\r\n", "\r\n", " First backup of my amazing idea\r\n" ] } ], "prompt_number": 70 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Why are these two outputs the same?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git branch - which branch are we on?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "We haven't covered branches yet. Branches are bookmarks. They label the commit we are on at the moment, and they track the commits as we do new ones." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "The default branch for git is called `master`. Git creates it automatically when you do your first commit." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[32mmaster\u001b[m\r\n" ] } ], "prompt_number": 71 }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch -v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[32mmaster\u001b[m 1d7415e First backup of my amazing idea\r\n" ] } ], "prompt_number": 72 }, { "cell_type": "code", "collapsed": false, "input": [ "!ls .git/refs/heads" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "master\r\n" ] } ], "prompt_number": 73 }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/refs/heads/master" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "1d7415e14ef0d556a97630997644307942f7f703\r\n" ] } ], "prompt_number": 74 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git diff - what has changed?" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's do a little bit more work... Again, in practice you'll be editing the files by hand, here we do it via shell commands for the sake of automation (and therefore the reproducibility of this tutorial!)" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!echo \"\\nThe charts are very impressive\\n\" >> nobel_prize.txt" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 75 }, { "cell_type": "markdown", "metadata": {}, "source": [ "And now we can ask git what is different:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git diff" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u001b[1mdiff --git a/nobel_prize.txt b/nobel_prize.txt\u001b[m\r\n", "\u001b[1mindex d92d079..47b7547 100644\u001b[m\r\n", "\u001b[1m--- a/nobel_prize.txt\u001b[m\r\n", "\u001b[1m+++ b/nobel_prize.txt\u001b[m\r\n", "\u001b[36m@@ -4,3 +4,6 @@\u001b[m \u001b[mThe brain is just a set of random numbers\u001b[m\r\n", " We have discovered that the brain is a set of random numbers.\u001b[m\r\n", " \u001b[m\r\n", " We have charts and graphs to back us up.\u001b[m\r\n", "\u001b[32m+\u001b[m\r\n", "\u001b[32m+\u001b[m\u001b[32mThe charts are very impressive\u001b[m\r\n", "\u001b[41m+\u001b[m\r\n" ] } ], "prompt_number": 76 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### You need to `git add` a file to put it into the staging area" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember that git only commits stuff that has been added to the staging area.\n", "\n", "At the moment we have changes that have not been staged:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "# Changes not staged for commit:\r\n", "# (use \"git add ...\" to update what will be committed)\r\n", "# (use \"git checkout -- ...\" to discard changes in working directory)\r\n", "#\r\n", "#\t\u001b[31mmodified: nobel_prize.txt\u001b[m\r\n", "#\r\n", "no changes added to commit (use \"git add\" and/or \"git commit -a\")\r\n" ] } ], "prompt_number": 77 }, { "cell_type": "markdown", "metadata": {}, "source": [ "If we try and do a commit, git will tell us there is nothing to commit, because nothing has been staged:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git commit" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "# Changes not staged for commit:\r\n", "# (use \"git add ...\" to update what will be committed)\r\n", "# (use \"git checkout -- ...\" to discard changes in working directory)\r\n", "#\r\n", "#\t\u001b[31mmodified: nobel_prize.txt\u001b[m\r\n", "#\r\n", "no changes added to commit (use \"git add\" and/or \"git commit -a\")\r\n" ] } ], "prompt_number": 78 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### The cycle of git virtue: work, commit, work, commit, ..." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "# Changes not staged for commit:\r\n", "# (use \"git add ...\" to update what will be committed)\r\n", "# (use \"git checkout -- ...\" to discard changes in working directory)\r\n", "#\r\n", "#\t\u001b[31mmodified: nobel_prize.txt\u001b[m\r\n", "#\r\n", "no changes added to commit (use \"git add\" and/or \"git commit -a\")\r\n" ] } ], "prompt_number": 79 }, { "cell_type": "code", "collapsed": false, "input": [ "!git add nobel_prize.txt" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 80 }, { "cell_type": "code", "collapsed": false, "input": [ "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "# Changes to be committed:\r\n", "# (use \"git reset HEAD ...\" to unstage)\r\n", "#\r\n", "#\t\u001b[32mmodified: nobel_prize.txt\u001b[m\r\n", "#\r\n" ] } ], "prompt_number": 81 }, { "cell_type": "code", "collapsed": false, "input": [ "!git commit -m \"Fruit of enormous thought\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[master ceda705] Fruit of enormous thought\r\n", " 1 file changed, 3 insertions(+)\r\n" ] } ], "prompt_number": 82 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Remember branches?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/refs/heads/master" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "ceda7056aba330314aa219f7facbb00eb1a03248\r\n" ] } ], "prompt_number": 83 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git log revisited" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "First, let's see what the log shows us now:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git log" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u001b[33mcommit ceda7056aba330314aa219f7facbb00eb1a03248\u001b[m\r\n", "Author: Matthew Brett \r\n", "Date: Thu May 2 22:04:59 2013 -0700\r\n", "\r\n", " Fruit of enormous thought\r\n", "\r\n", "\u001b[33mcommit 1d7415e14ef0d556a97630997644307942f7f703\u001b[m\r\n", "Author: Matthew Brett \r\n", "Date: Thu May 2 22:04:55 2013 -0700\r\n", "\r\n", " First backup of my amazing idea\r\n" ] } ], "prompt_number": 84 }, { "cell_type": "code", "collapsed": false, "input": [ "!git log --parents" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "\u001b[33mcommit ceda7056aba330314aa219f7facbb00eb1a03248 1d7415e14ef0d556a97630997644307942f7f703\u001b[m\r\n", "Author: Matthew Brett \r\n", "Date: Thu May 2 22:04:59 2013 -0700\r\n", "\r\n", " Fruit of enormous thought\r\n", "\r\n", "\u001b[33mcommit 1d7415e14ef0d556a97630997644307942f7f703\u001b[m\r\n", "Author: Matthew Brett \r\n", "Date: Thu May 2 22:04:55 2013 -0700\r\n", "\r\n", " First backup of my amazing idea\r\n" ] } ], "prompt_number": 85 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Making log output more pithy" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Sometimes it's handy to see a very summarized version of the log:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git log --oneline --topo-order --graph" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 86 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Git supports *aliases:* new names given to command combinations. Let's make this handy shortlog an alias, so we only have to type `git slog` and see this compact log:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "# We create our alias (this saves it in git's permanent configuration file):\n", "!git config --global alias.slog \"log --oneline --topo-order --graph\"\n", "\n", "# And now we can use it\n", "!git slog" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 87 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### `git mv` and `rm`: moving and removing files" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While `git add` is used to add files to the list git tracks, we must also tell it if we want their names to change or for it to stop tracking them. In familiar Unix fashion, the `mv` and `rm` git commands do precisely this:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git mv very_clever_analysis.py slightly_dodgy_analysis.py\n", "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "# Changes to be committed:\r\n", "# (use \"git reset HEAD ...\" to unstage)\r\n", "#\r\n", "#\t\u001b[32mrenamed: very_clever_analysis.py -> slightly_dodgy_analysis.py\u001b[m\r\n", "#\r\n" ] } ], "prompt_number": 88 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Note that these changes must be committed too, to become permanent! In git's world, until something has been committed, it isn't permanently recorded anywhere." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git commit -m \"I like this new name better\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[master 01df2e1] I like this new name better\r\n", " 1 file changed, 0 insertions(+), 0 deletions(-)\r\n", " rename very_clever_analysis.py => slightly_dodgy_analysis.py (100%)\r\n" ] } ], "prompt_number": 89 }, { "cell_type": "code", "collapsed": false, "input": [ "!git slog" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33m01df2e1\u001b[m I like this new name better\r\n", "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 90 }, { "cell_type": "markdown", "metadata": {}, "source": [ "And `git rm` works in a similar fashion." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Exercise" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Add a new file `file2.txt`, commit it, make some changes to it, commit them again, and then remove it (and don't forget to commit this last step!)." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Local user, branching" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "What is a branch? Simply a *label for the 'current' commit in a sequence of ongoing commits*:\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "There can be multiple branches alive at any point in time; the working directory is the state of a special pointer called HEAD. In this example there are two branches, *master* and *testing*, and *testing* is the currently active branch since it's what HEAD points to:\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/HEAD" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "ref: refs/heads/master\r\n" ] } ], "prompt_number": 91 }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/refs/heads/master" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "01df2e1271fe12e0586f3a3876a567c684d6ce9b\r\n" ] } ], "prompt_number": 92 }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch -v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[32mmaster\u001b[m 01df2e1 I like this new name better\r\n" ] } ], "prompt_number": 93 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Once new commits are made on a branch, HEAD and the branch label move with the new commits:\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "This allows the history of both branches to diverge:\n", "\n", "\n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "But based on this graph structure, git can compute the necessary information to merge the divergent branches back and continue with a unified line of development:\n", "\n", " \n", "\n", "\n", "\n", "\n", "\n", "" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Let's now illustrate all of this with a concrete example. Let's get our bearings first:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git status\n", "!ls" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "nothing to commit (working directory clean)\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "nobel_prize.txt slightly_dodgy_analysis.py stunning_figure.png\r\n" ] } ], "prompt_number": 94 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "We are now going to try two different routes of development: on the `master` branch we will add one file and on the `experiment` branch, which we will create, we will add a different one. We will then merge the experimental branch into `master`." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch experiment" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 95 }, { "cell_type": "markdown", "metadata": {}, "source": [ "What just happened? We made a branch, which is a pointer to this same commit:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!ls .git/refs/heads" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "experiment master\r\n" ] } ], "prompt_number": 96 }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch -v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ " experiment\u001b[m 01df2e1 I like this new name better\r\n", "* \u001b[32mmaster \u001b[m 01df2e1 I like this new name better\r\n" ] } ], "prompt_number": 97 }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/refs/heads/experiment" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "01df2e1271fe12e0586f3a3876a567c684d6ce9b\r\n" ] } ], "prompt_number": 98 }, { "cell_type": "markdown", "metadata": {}, "source": [ "How do we start working on this branch `experiment` rather than `master`?" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git checkout - set the current branch, set the working tree from a commit" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "Up until now we have been on the `master` branch. When we make a commit, the `master` branch pointer (`.git/refs/heads/master`) moves up to track our most recent commit.\n", "\n", "`git checkout` can switch us to using another branch:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git checkout experiment" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Switched to branch 'experiment'\r\n" ] } ], "prompt_number": 99 }, { "cell_type": "markdown", "metadata": {}, "source": [ "What just happened?" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch -v" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[32mexperiment\u001b[m 01df2e1 I like this new name better\r\n", " master \u001b[m 01df2e1 I like this new name better\r\n" ] } ], "prompt_number": 100 }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/HEAD" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "ref: refs/heads/experiment\r\n" ] } ], "prompt_number": 101 }, { "cell_type": "markdown", "metadata": {}, "source": [ "As we'll see later, `git checkout somebranch` also sets the contents of the working tree to match the commit contents for `somebranch`. In this case the commits for `master` and `experiment` are currently the same, so we don't change the working tree at all." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Now let's do some changes on our `experiment` branch:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!echo \"Some crazy idea\" > experiment.txt\n", "!git add experiment.txt\n", "!git commit -m \"Trying something new\"\n", "!git slog" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[experiment b83cd47] Trying something new\r\n", " 1 file changed, 1 insertion(+)\r\n", " create mode 100644 experiment.txt\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33mb83cd47\u001b[m Trying something new\r\n", "* \u001b[33m01df2e1\u001b[m I like this new name better\r\n", "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 102 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Notice we have a new file called `experiment.txt` in this branch: " ] }, { "cell_type": "code", "collapsed": false, "input": [ "!ls" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "experiment.txt nobel_prize.txt slightly_dodgy_analysis.py stunning_figure.png\r\n" ] } ], "prompt_number": 103 }, { "cell_type": "markdown", "metadata": {}, "source": [ "The `experiment` branch has now moved:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat .git/refs/heads/experiment" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "b83cd47540cb7dbd7d16292d8a35417f031716f9\r\n" ] } ], "prompt_number": 104 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### git checkout again - reseting the working tree" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If `somewhere` is a branch name, then `git checkout somewhere` selects `somewhere` as the current branch. It also resets the working tree to match the working tree for that commit." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git checkout master\n", "!cat .git/HEAD" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Switched to branch 'master'\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "ref: refs/heads/master\r\n" ] } ], "prompt_number": 105 }, { "cell_type": "markdown", "metadata": {}, "source": [ "We're back to the working tree as of the `master` branch; `experiment.txt` has gone now." ] }, { "cell_type": "code", "collapsed": false, "input": [ "!ls" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "nobel_prize.txt slightly_dodgy_analysis.py stunning_figure.png\r\n" ] } ], "prompt_number": 106 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Meanwhile we do some more work on master:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git slog" ], "language": "python", "metadata": { "slideshow": { "slide_type": "-" } }, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33m01df2e1\u001b[m I like this new name better\r\n", "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 107 }, { "cell_type": "code", "collapsed": false, "input": [ "!echo \"All the while, more work goes on in master...\" >> nobel_prize.txt\n", "!git add nobel_prize.txt\n", "!git commit -m \"The mainline keeps moving\"\n", "!git slog" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[master 5c436d0] The mainline keeps moving\r\n", " 1 file changed, 1 insertion(+)\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33m5c436d0\u001b[m The mainline keeps moving\r\n", "* \u001b[33m01df2e1\u001b[m I like this new name better\r\n", "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 108 }, { "cell_type": "code", "collapsed": false, "input": [ "!ls" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "nobel_prize.txt slightly_dodgy_analysis.py stunning_figure.png\r\n" ] } ], "prompt_number": 109 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Now let's do the merge" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git merge experiment -m \"Merge in the experiment\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Merge made by the 'recursive' strategy.\r\n", " experiment.txt | 1 \u001b[32m+\u001b[m\r\n", " 1 file changed, 1 insertion(+)\r\n", " create mode 100644 experiment.txt\r\n" ] } ], "prompt_number": 110 }, { "cell_type": "code", "collapsed": false, "input": [ "!git slog" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33mb444163\u001b[m Merge in the experiment\r\n", "\u001b[31m|\u001b[m\u001b[32m\\\u001b[m \r\n", "\u001b[31m|\u001b[m * \u001b[33mb83cd47\u001b[m Trying something new\r\n", "* \u001b[32m|\u001b[m \u001b[33m5c436d0\u001b[m The mainline keeps moving\r\n", "\u001b[32m|\u001b[m\u001b[32m/\u001b[m \r\n", "* \u001b[33m01df2e1\u001b[m I like this new name better\r\n", "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 111 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### An important aside: conflict management" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "While git is very good at merging, if two different branches modify the same file in the same location, it simply can't decide which change should prevail. At that point, human intervention is necessary to make the decision. Git will help you by marking the location in the file that has a problem, but it's up to you to resolve the conflict. Let's see how that works by intentionally creating a conflict.\n", "\n", "\n", "\n", "We start by creating a branch and making a change to our experiment file:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git branch trouble\n", "!git checkout trouble\n", "!echo \"This is going to be a problem...\" >> experiment.txt\n", "!git add experiment.txt\n", "!git commit -m\"Changes in the trouble branch\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Switched to branch 'trouble'\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "[trouble 6921250] Changes in the trouble branch\r\n", " 1 file changed, 1 insertion(+)\r\n" ] } ], "prompt_number": 112 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "And now we go back to the master branch, where we change the *same* file:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git checkout master\n", "!echo \"More work on the master branch...\" >> experiment.txt\n", "!git add experiment.txt\n", "!git commit -m\"Mainline work\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Switched to branch 'master'\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "[master 1ebca55] Mainline work\r\n", " 1 file changed, 1 insertion(+)\r\n" ] } ], "prompt_number": 113 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### More configuration for fuller output" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This line tells git how to show conflicts in text files.\n", "\n", "See `git config --help` and look for `merge.conflictstyle` for more information.\n", "\n", "This setting asks git to show:\n", " \n", "* The original file contents from `HEAD`, the commit tree that you are merging *into*\n", "* The contents of the file as of last common ancestor commit\n", "* The new file contents from the commit tree that you are merging *from*" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git config merge.conflictstyle diff3" ], "language": "python", "metadata": {}, "outputs": [], "prompt_number": 114 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "So now let's see what happens if we try to merge the `trouble` branch into `master`:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git merge trouble -m \"Unlikely this one will work\"" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Auto-merging experiment.txt\r\n", "CONFLICT (content): Merge conflict in experiment.txt\r\n", "Automatic merge failed; fix conflicts and then commit the result.\r\n" ] } ], "prompt_number": 115 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Let's see what git has put into our file:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!cat experiment.txt" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Some crazy idea\r\n", "<<<<<<< HEAD\r\n", "More work on the master branch...\r\n", "||||||| merged common ancestors\r\n", "=======\r\n", "This is going to be a problem...\r\n", ">>>>>>> trouble\r\n" ] } ], "prompt_number": 116 }, { "cell_type": "markdown", "metadata": {}, "source": [ "Read this as:\n", " \n", "* Common ancestor (between `||`... and `==`) has nothing;\n", "* `HEAD` - the current branch - (between `<<`... and `||`) adds `More work on the master branch...`;\n", "* the `trouble` branch (between `==`... and `>>`...) adds `This is going to be a problem...`." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "At this point, we go into the file with a text editor, decide which changes to keep, and make a new commit that records our decision.\n", "\n", "I'll do the edits by writing the file I want directly in this case:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "%%file experiment.txt\n", "Some crazy idea\n", "More work on the master branch...\n", "This is going to be a problem..." ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "Overwriting experiment.txt" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "\n" ] } ], "prompt_number": 117 }, { "cell_type": "markdown", "metadata": {}, "source": [ "I've now made the edits. I decided that both pieces of text were useful, but integrated them with some changes:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git status" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "# On branch master\r\n", "# You have unmerged paths.\r\n", "# (fix conflicts and run \"git commit\")\r\n", "#\r\n", "# Unmerged paths:\r\n", "# (use \"git add ...\" to mark resolution)\r\n", "#\r\n", "#\t\u001b[31mboth modified: experiment.txt\u001b[m\r\n", "#\r\n", "no changes added to commit (use \"git add\" and/or \"git commit -a\")\r\n" ] } ], "prompt_number": 118 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "Let's then make our new commit:" ] }, { "cell_type": "code", "collapsed": false, "input": [ "!git add experiment.txt\n", "!git commit -m \"Completed merge of trouble, fixing conflicts along the way\"\n", "!git slog" ], "language": "python", "metadata": {}, "outputs": [ { "output_type": "stream", "stream": "stdout", "text": [ "[master 51fd6ff] Completed merge of trouble, fixing conflicts along the way\r\n" ] }, { "output_type": "stream", "stream": "stdout", "text": [ "* \u001b[33m51fd6ff\u001b[m Completed merge of trouble, fixing conflicts along the way\r\n", "\u001b[31m|\u001b[m\u001b[32m\\\u001b[m \r\n", "\u001b[31m|\u001b[m * \u001b[33m6921250\u001b[m Changes in the trouble branch\r\n", "* \u001b[32m|\u001b[m \u001b[33m1ebca55\u001b[m Mainline work\r\n", "\u001b[32m|\u001b[m\u001b[32m/\u001b[m \r\n", "* \u001b[33mb444163\u001b[m Merge in the experiment\r\n", "\u001b[33m|\u001b[m\u001b[34m\\\u001b[m \r\n", "\u001b[33m|\u001b[m * \u001b[33mb83cd47\u001b[m Trying something new\r\n", "* \u001b[34m|\u001b[m \u001b[33m5c436d0\u001b[m The mainline keeps moving\r\n", "\u001b[34m|\u001b[m\u001b[34m/\u001b[m \r\n", "* \u001b[33m01df2e1\u001b[m I like this new name better\r\n", "* \u001b[33mceda705\u001b[m Fruit of enormous thought\r\n", "* \u001b[33m1d7415e\u001b[m First backup of my amazing idea\r\n" ] } ], "prompt_number": 119 }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Other useful commands" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "- [show](http://www.kernel.org/pub/software/scm/git/docs/git-show.html)\n", "\n", "- [reflog](http://www.kernel.org/pub/software/scm/git/docs/git-reflog.html)\n", "\n", "- [rebase](http://www.kernel.org/pub/software/scm/git/docs/git-rebase.html)\n", "\n", "- [tag](http://www.kernel.org/pub/software/scm/git/docs/git-tag.html)" ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "## Git resources" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "### Introductory materials" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "There are lots of good tutorials and introductions for Git, which you\n", "can easily find yourself; this is just a short list of things I've found\n", "useful. For a beginner, I would recommend the following 'core' reading list, and\n", "below I mention a few extra resources:\n", "\n", "1. The smallest, and in the style of this tuorial: [git - the simple guide](http://rogerdudler.github.com/git-guide)\n", "contains 'just the basics'. Very quick read.\n", "\n", "1. The concise [Git Reference](http://gitref.org): compact but with\n", " all the key ideas. If you only read one document, make it this one.\n", "\n", "1. In my own experience, the most useful resource was [Understanding Git\n", "Conceptually](http://www.sbf5.com/~cduan/technical/git).\n", "Git has a reputation for being hard to use, but I have found that with a\n", "clear view of what is actually a *very simple* internal design, its\n", "behavior is remarkably consistent, simple and comprehensible.\n", "\n", "1. For more detail, see the start of the excellent [Pro\n", " Git](http://progit.org/book) online book, or similarly the early\n", " parts of the [Git community book](http://book.git-scm.com). Pro\n", " Git's chapters are very short and well illustrated; the community\n", " book tends to have more detail and has nice screencasts at the end\n", " of some sections.\n", "\n", "If you are really impatient and just want a quick start, this [visual git tutorial](http://www.ralfebert.de/blog/tools/visual_git_tutorial_1)\n", "may be sufficient. It is nicely illustrated with diagrams that show what happens on the filesystem.\n", "\n", "For windows users, [an Illustrated Guide to Git on Windows](http://nathanj.github.com/gitguide/tour.html) is useful in that\n", "it contains also some information about handling SSH (necessary to interface with git hosted on remote servers when collaborating) as well\n", "as screenshots of the Windows interface.\n", "\n", "Cheat sheets\n", ": Two different\n", " [cheat](http://zrusin.blogspot.com/2007/09/git-cheat-sheet.html)\n", " [sheets](http://jan-krueger.net/development/git-cheat-sheet-extended-edition)\n", " in PDF format that can be printed for frequent reference." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### Beyond the basics" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "As you've seen, this tutorial makes the bold assumption that you'll be able to understand how git works by seeing how it is *built*. These two documents, written in a similar spirit, \n", "are probably the most useful descriptions of the Git architecture short of diving into the actual implementation. They walk you through\n", "how you would go about building a version control system with a little story. By the end you realize that Git's model is almost\n", "an inevitable outcome of the proposed constraints:\n", "\n", "* The [Git parable](http://tom.preston-werner.com/2009/05/19/the-git-parable.html) by Tom Preston-Werner.\n", "* [Git foundations](http://matthew-brett.github.com/pydagogue/foundation.html) by Matthew Brett.\n", "\n", "[Git ready](http://www.gitready.com)\n", ": A great website of posts on specific git-related topics, organized\n", " by difficulty.\n", "\n", "[QGit](http://sourceforge.net/projects/qgit/): an excellent Git GUI\n", ": Git ships by default with gitk and git-gui, a pair of Tk graphical\n", " clients to browse a repo and to operate in it. I personally have\n", " found [qgit](http://sourceforge.net/projects/qgit/) to be nicer and\n", " easier to use. It is available on modern linux distros, and since it\n", " is based on Qt, it should run on OSX and Windows.\n", "\n", "[Git Magic](http://www-cs-students.stanford.edu/~blynn/gitmagic/index.html)\n", ": Another book-size guide that has useful snippets.\n", "\n", "The [learning center](http://learn.github.com) at Github\n", ": Guides on a number of topics, some specific to github hosting but\n", " much of it of general value.\n", "\n", "A [port](http://cworth.org/hgbook-git/tour) of the Hg book's beginning\n", ": The [Mercurial book](http://hgbook.red-bean.com) has a reputation\n", " for clarity, so Carl Worth decided to\n", " [port](http://cworth.org/hgbook-git/tour) its introductory chapter\n", " to Git. It's a nicely written intro, which is possible in good\n", " measure because of how similar the underlying models of Hg and Git\n", " ultimately are.\n", "\n", "[Intermediate tips](http://andyjeffries.co.uk/articles/25-tips-for-intermediate-git-users)\n", ": A set of tips that contains some very valuable nuggets, once you're\n", " past the basics." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### For SVN users" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "If you want a bit more background on why the model of version control\n", "used by Git and Mercurial (known as distributed version control) is such\n", "a good idea, I encourage you to read this very well written\n", "[post](http://www.joelonsoftware.com/items/2010/03/17.html) by Joel\n", "Spolsky on the topic. After that post, Joel created a very nice\n", "Mercurial tutorial, whose [first page](http://hginit.com/00.html)\n", "applies equally well to git and is a very good 're-education' for anyone\n", "coming from an SVN (or similar) background.\n", "\n", "In practice, I think you are better off following Joel's advice and\n", "understanding git on its own merits instead of trying to bang SVN\n", "concepts into git shapes. But for the occasional translation from SVN to\n", "Git of a specific idiom, the [Git - SVN Crash\n", "Course](http://git-scm.org/course/svn.html) can be handy." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "### A few useful tips for common tasks" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### Better shell support\n", "\n", "\n", "\n", "Adding git branch info to your bash prompt and tab completion for git commands and branches is extremely useful. I suggest you at least copy:\n", "\n", "\n", "\n", "- [git-completion.bash](https://github.com/git/git/blob/master/contrib/completion/git-completion.bash)\n", "\n", "- [git-prompt.sh](https://github.com/git/git/blob/master/contrib/completion/git-prompt.sh)\n", "\n", " \n", "\n", "You can then source both of these files in your `~/.bashrc` (Linux) or `~/.bash_profile` (OSX), and then set your prompt (I'll assume you named them as the originals but starting with a `.` at the front of the name):\n", "\n", "\n", " source $HOME/.git-completion.bash\n", "\n", " source $HOME/.git-prompt.sh\n", "\n", " PS1='[\\u@\\h \\W$(__git_ps1 \" (%s)\")]\\$ ' # adjust this to your prompt liking\n", "\n", "\n", "\n", "See the comments in both of those files for lots of extra functionality they offer." ] }, { "cell_type": "markdown", "metadata": { "slideshow": { "slide_type": "slide" } }, "source": [ "#### Embedding Git information in LaTeX documents\n", "\n", "(Sent by [Yaroslav Halchenko](http://www.onerussian.com))\n", "\n", "I use a Make rule:\n", "\n", " # Helper if interested in providing proper version tag within the manuscript\n", " revision.tex: ../misc/revision.tex.in ../.git/index\n", " GITID=$$(git log -1 | grep -e '^commit' -e '^Date:' | sed -e 's/^[^ ]* *//g' | tr '\\n' ' '); \\\n", " echo $$GITID; \\\n", " sed -e \"s/GITID/$$GITID/g\" $< >| $@\n", "\n", "in the top level `Makefile.common` which is included in all\n", "subdirectories which actually contain papers (hence all those\n", "`../.git`). The `revision.tex.in` file is simply:\n", "\n", " % Embed GIT ID revision and date\n", " \\def\\revision{GITID}\n", "\n", "The corresponding `paper.pdf` depends on `revision.tex` and includes the\n", "line `\\input{revision}` to load up the actual revision mark." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "#### git export\n", "\n", "\n", "\n", "Git doesn't have a native export command, but this works just fine:\n", "\n", "\n", "\n", " git archive --prefix=fperez.org/ master | gzip > ~/tmp/source.tgz" ] } ], "metadata": {} } ] }