tutorial: finish documenting all tree operations
authorHervé Cauwelier <herve@oursours.net>
Mon, 21 Sep 2009 20:36:17 +0000 (22:36 +0200)
committerHervé Cauwelier <herve@oursours.net>
Mon, 21 Sep 2009 20:36:17 +0000 (22:36 +0200)
docs/tutorial/.gitignore
docs/tutorial/1-initial-commit.txt
docs/tutorial/2-change-file.txt [moved from docs/tutorial/2-change_file.txt with 87% similarity]
docs/tutorial/3-add-file.txt [new file with mode: 0644]
docs/tutorial/4-remove-file.txt [new file with mode: 0644]
docs/tutorial/5-rename-file.txt [new file with mode: 0644]
docs/tutorial/6-conclusion.txt [new file with mode: 0644]
docs/tutorial/index.txt
docs/tutorial/test.py [new file with mode: 0755]

index 2d19fc766d98a08d9d1437896bfb008a7b15f340..6b71f091c44526412a52114023133e3a56aa100e 100644 (file)
@@ -1 +1,2 @@
 *.html
+myrepo
index 62d02a2b32a70ea162ebb17407bd1de47a8e8906..f0a71a7599bee987f1999bcf5de1674afd449df9 100644 (file)
@@ -34,9 +34,9 @@ When you use Git, you generally add or modify content. As our repository is
 empty for now, we'll start by adding a new file::
 
   >>> from dulwich.objects import Blob
-  >>> blob = Blob.from_string("My file content")
+  >>> blob = Blob.from_string("My file content\n")
   >>> blob.id
-  '456a1e689eb87b947be24562e830421cd799388c'
+  'c55063a4d5d37aa1af2b2dad3a70aa34dae54dc6'
 
 Of course you could create a blob from an existing file using ``from_file``
 instead.
similarity index 87%
rename from docs/tutorial/2-change_file.txt
rename to docs/tutorial/2-change-file.txt
index 4f75c2fc7f1c50d1c7cfa91fede933f3d2217dd4..4b722280cd7ee54639856c33149031c833109f57 100644 (file)
@@ -12,15 +12,15 @@ object from the previous chapter.
 
 Let's first build the blob::
 
-  >>> spam = Blob.from_string("My new file content")
+  >>> spam = Blob.from_string("My new file content\n")
   >>> spam.id
-  'fd2a0fa3ad828c5bda4b7badcbe522dc3b12af73'
+  '16ee2682887a962f854ebd25a61db16ef4efe49f'
 
 An alternative is to alter the previously constructed blob object::
 
-  >>> blob.data = "My new file content"
+  >>> blob.data = "My new file content\n"
   >>> blob.id
-  'fd2a0fa3ad828c5bda4b7badcbe522dc3b12af73'
+  '16ee2682887a962f854ebd25a61db16ef4efe49f'
 
 In any case, update the blob id known as "spam". You also have the
 opportunity of changing its mode::
@@ -49,7 +49,7 @@ Remain to record this whole new family::
   >>> object_store.add_object(c2)
 
 You can already ask git to introspect this commit using ``git show`` and the
-value of ``commit.it`` as an argument. You'll see the difference will the
+value of ``commit.id`` as an argument. You'll see the difference will the
 previous blob recorded as "spam".
 
 You won't see it using git log because the head is still the previous
diff --git a/docs/tutorial/3-add-file.txt b/docs/tutorial/3-add-file.txt
new file mode 100644 (file)
index 0000000..75d6429
--- /dev/null
@@ -0,0 +1,41 @@
+Adding a file
+=============
+
+If you followed well, the next lesson will be straightforward.
+
+We need a new blob::
+
+    >>> ham = Blob.from_string("Another\nmultiline\nfile\n")
+    >>> ham.id
+    'a3b5eda0b83eb8fb6e5dce91ecafda9e97269c70'
+
+But the same tree::
+
+    >>> tree["ham"] = (0100644, spam.id)
+
+And a new commit::
+
+  >>> c3 = Commit()
+  >>> c3.tree = tree.id
+  >>> c3.parents = [commit.id]
+  >>> c3.author = c3.committer = author
+  >>> c3.commit_time = c3.author_time = int(time())
+  >>> c3.commit_timezone = c3.author_timezone = tz
+  >>> c3.encoding = "UTF-8"
+  >>> c3.message = 'Adding "ham"'
+
+Save it all::
+
+    >>> object_store.add_object(spam)
+    >>> object_store.add_object(tree)
+    >>> object_store.add_object(c3)
+
+Update the head::
+
+    >>> repo.refs['refs/heads/master'] = commit.id
+
+A call to ``git show`` will confirm the addition of "spam".
+
+Remember you can also call ``git checkout -f`` to make it appear.
+
+Well... Adding "spam" was not such a good idea... We'll remove it.
diff --git a/docs/tutorial/4-remove-file.txt b/docs/tutorial/4-remove-file.txt
new file mode 100644 (file)
index 0000000..125639c
--- /dev/null
@@ -0,0 +1,30 @@
+Removing a file
+===============
+
+Removing a file just means removing its entry in the tree. The blob won't be
+deleted because Git tries to preserve the history of your repository.
+
+It's all pythonic::
+
+    >>> del tree["ham"]
+
+  >>> c4 = Commit()
+  >>> c4.tree = tree.id
+  >>> c4.parents = [commit.id]
+  >>> c4.author = c4.committer = author
+  >>> c4.commit_time = c4.author_time = int(time())
+  >>> c4.commit_timezone = c4.author_timezone = tz
+  >>> c4.encoding = "UTF-8"
+  >>> c4.message = 'Removing "ham"'
+
+Here we only have the new tree and the commit to save::
+
+    >>> object_store.add_object(spam)
+    >>> object_store.add_object(tree)
+    >>> object_store.add_object(c4)
+
+And of course update the head::
+
+    >>> repo.refs['refs/heads/master'] = commit.id
+
+If you don't trust me, ask ``git show``. ;-)
diff --git a/docs/tutorial/5-rename-file.txt b/docs/tutorial/5-rename-file.txt
new file mode 100644 (file)
index 0000000..15c6b26
--- /dev/null
@@ -0,0 +1,33 @@
+Renaming a file
+===============
+
+Remember you learned that the file name and content are distinct. So renaming
+a file is just about associating a blob id to a new name. We won't store more
+content, and the operation will be painless.
+
+Let's transfer the blob id from the old name to the new one::
+
+    >>> tree["eggs"] = tree["spam"]
+    >>> del tree["spam"]
+
+As usual, we need a commit to store the new tree id::
+
+  >>> c5 = Commit()
+  >>> c5.tree = tree.id
+  >>> c5.parents = [commit.id]
+  >>> c5.author = c5.committer = author
+  >>> c5.commit_time = c5.author_time = int(time())
+  >>> c5.commit_timezone = c5.author_timezone = tz
+  >>> c5.encoding = "UTF-8"
+  >>> c5.message = 'Rename "spam" to "eggs"'
+
+As for a deletion, we only have a tree and a commit to save::
+
+    >>> object_store.add_object(tree)
+    >>> object_store.add_object(c5)
+
+Remains to make the head bleeding-edge::
+
+    >>> repo.refs['refs/heads/master'] = commit.id
+
+As a last exercise, see how ``git show`` illustrates it.
diff --git a/docs/tutorial/6-conclusion.txt b/docs/tutorial/6-conclusion.txt
new file mode 100644 (file)
index 0000000..bc8092f
--- /dev/null
@@ -0,0 +1,14 @@
+Conclusion
+==========
+
+You'll find the ``test.py`` program with some tips I use to ease generating
+objects.
+
+You can also make Tag objects, but this is left as a exercise to the reader.
+
+Dulwich is abstracting  much of the Git plumbing, so there would be more to
+see.
+
+Dulwich is also able to clone and push repositories.
+
+That's all folks!
index b6baa9a23e64228356c71bef8f07f5a85cbda1b1..f2a06bdedcf0ec2cdd2b5dcb49c15f71a4354224 100644 (file)
@@ -6,4 +6,8 @@ Dulwich Tutorial
 
 .. include:: 0-introduction.txt
 .. include:: 1-initial-commit.txt
-.. include:: 2-change_file.txt
+.. include:: 2-change-file.txt
+.. include:: 3-add-file.txt
+.. include:: 4-remove-file.txt
+.. include:: 5-rename-file.txt
+.. include:: 6-conclusion.txt
diff --git a/docs/tutorial/test.py b/docs/tutorial/test.py
new file mode 100755 (executable)
index 0000000..058ca4b
--- /dev/null
@@ -0,0 +1,178 @@
+#!/usr/bin/env python
+# -*- encoding: UTF-8 -*-
+
+# Import from the Standard Library
+from os import F_OK, access, mkdir
+from pprint import pprint
+from shutil import rmtree
+from subprocess import call
+from time import time
+
+# Import from dulwich
+from dulwich.repo import Repo
+from dulwich.objects import Blob, Tree, Commit, parse_timezone
+
+
+DIRNAME = "myrepo"
+AUTHOR = "Your Name <your.email@example.com>"
+TZ = parse_timezone('-200')
+ENCODING = "UTF-8"
+
+
+def make_commit(repo, tree_id, message):
+    """Build a commit object on the same pattern. Only changing values are
+    required as parameters.
+    """
+    commit = Commit()
+    try:
+        commit.parents = [repo.head()]
+    except KeyError:
+        # The initial commit has no parent
+        pass
+    commit.tree = tree_id
+    commit.message = message
+    commit.author = commit.committer = AUTHOR
+    commit.commit_time = commit.author_time = int(time())
+    commit.commit_timezone = commit.author_timezone = TZ
+    commit.encoding = ENCODING
+    return commit
+
+
+
+def make_tree(repo):
+    """Return the last known tree.
+    """
+    commit_id = repo.head()
+    commit = repo.commit(commit_id)
+    tree_id = commit.tree
+    return repo.tree(tree_id)
+
+
+
+def update_master(repo, commit_id):
+    repo.refs['refs/heads/master'] = commit_id
+
+
+
+def initial_commit(repo):
+    # Add file content
+    blob = Blob.from_string("My file content\n")
+    # Add file
+    tree = Tree()
+    tree.add(0100644, "spam", blob.id)
+    # Set commit
+    commit = make_commit(repo, tree.id, "Initial commit")
+    # Initial commit
+    object_store = repo.object_store
+    object_store.add_object(blob)
+    object_store.add_object(tree)
+    object_store.add_object(commit)
+    # Update master
+    update_master(repo, commit.id)
+    # Set the master branch as the default
+    repo.refs['HEAD'] = 'ref: refs/heads/master'
+
+
+
+def test_change(repo):
+    tree = make_tree(repo)
+    # Change a file
+    spam = Blob.from_string("My new file content\n")
+    tree.add(0100644, "spam", spam.id)
+    # Set commit
+    commit = make_commit(repo, tree.id, "Change spam")
+    # Second commit
+    object_store = repo.object_store
+    object_store.add_object(spam)
+    object_store.add_object(tree)
+    object_store.add_object(commit)
+    # Update master
+    update_master(repo, commit.id)
+
+
+
+def test_add(repo):
+    tree = make_tree(repo)
+    # Add another file
+    ham = Blob.from_string("Another\nmultiline\nfile\n")
+    tree.add(0100644, "ham", ham.id)
+    # Set commit
+    commit = make_commit(repo, tree.id, "Add ham")
+    # Second commit
+    object_store = repo.object_store
+    object_store.add_object(ham)
+    object_store.add_object(tree)
+    object_store.add_object(commit)
+    # Update master
+    update_master(repo, commit.id)
+
+
+
+def test_remove(repo):
+    tree = make_tree(repo)
+    # Remove a file
+    del tree["ham"]
+    # Set commit
+    commit = make_commit(repo, tree.id, 'Remove "ham"')
+    # Third commit
+    # No blob change, just tree operation
+    object_store = repo.object_store
+    object_store.add_object(tree)
+    object_store.add_object(commit)
+    # Update master
+    update_master(repo, commit.id)
+
+
+
+def test_rename(repo):
+    tree = make_tree(repo)
+    # Rename a file
+    tree["eggs"] = tree["spam"]
+    del tree["spam"]
+    # Set commit
+    commit = make_commit(repo, tree.id, 'Rename "spam" to "eggs"')
+    # Fourth commit
+    # No blob change, just tree operation
+    object_store = repo.object_store
+    object_store.add_object(tree)
+    object_store.add_object(commit)
+    # Update master
+    update_master(repo, commit.id)
+
+
+
+def test_history(repo):
+    pprint(repo.revision_history(repo.head()))
+
+
+
+def test_file(repo):
+    tree = make_tree(repo)
+    print "entries", tree.entries()
+    mode, blob_id = tree["eggs"]
+    blob = repo.get_blob(blob_id)
+    print "eggs", repr(blob.data)
+
+
+
+if __name__ == '__main__':
+    # Creating the repository
+    if access(DIRNAME, F_OK):
+        rmtree(DIRNAME)
+    mkdir(DIRNAME)
+    repo = Repo.init(DIRNAME)
+    initial_commit(repo)
+    test_change(repo)
+    test_add(repo)
+    test_remove(repo)
+    test_rename(repo)
+    last_commit_id = repo.head()
+    call(['git', 'gc'], cwd=DIRNAME)
+    # Re-load the repo
+    del repo
+    repo = Repo(DIRNAME)
+    # XXX the ref was removed and dulwich doesn't know where to read it
+    update_master(repo, last_commit_id)
+    assert last_commit_id == repo.head()
+    test_history(repo)
+    test_file(repo)