Browse Source

tutorial: finish documenting all tree operations

Hervé Cauwelier 15 years ago
parent
commit
6be3ae832e

+ 1 - 0
docs/tutorial/.gitignore

@@ -1 +1,2 @@
 *.html
 *.html
+myrepo

+ 2 - 2
docs/tutorial/1-initial-commit.txt

@@ -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::
 empty for now, we'll start by adding a new file::
 
 
   >>> from dulwich.objects import Blob
   >>> from dulwich.objects import Blob
-  >>> blob = Blob.from_string("My file content")
+  >>> blob = Blob.from_string("My file content\n")
   >>> blob.id
   >>> blob.id
-  '456a1e689eb87b947be24562e830421cd799388c'
+  'c55063a4d5d37aa1af2b2dad3a70aa34dae54dc6'
 
 
 Of course you could create a blob from an existing file using ``from_file``
 Of course you could create a blob from an existing file using ``from_file``
 instead.
 instead.

+ 5 - 5
docs/tutorial/2-change_file.txt → docs/tutorial/2-change-file.txt

@@ -12,15 +12,15 @@ object from the previous chapter.
 
 
 Let's first build the blob::
 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
   >>> spam.id
-  'fd2a0fa3ad828c5bda4b7badcbe522dc3b12af73'
+  '16ee2682887a962f854ebd25a61db16ef4efe49f'
 
 
 An alternative is to alter the previously constructed blob object::
 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
   >>> blob.id
-  'fd2a0fa3ad828c5bda4b7badcbe522dc3b12af73'
+  '16ee2682887a962f854ebd25a61db16ef4efe49f'
 
 
 In any case, update the blob id known as "spam". You also have the
 In any case, update the blob id known as "spam". You also have the
 opportunity of changing its mode::
 opportunity of changing its mode::
@@ -49,7 +49,7 @@ Remain to record this whole new family::
   >>> object_store.add_object(c2)
   >>> object_store.add_object(c2)
 
 
 You can already ask git to introspect this commit using ``git show`` and the
 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".
 previous blob recorded as "spam".
 
 
 You won't see it using git log because the head is still the previous
 You won't see it using git log because the head is still the previous

+ 41 - 0
docs/tutorial/3-add-file.txt

@@ -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.

+ 30 - 0
docs/tutorial/4-remove-file.txt

@@ -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``. ;-)

+ 33 - 0
docs/tutorial/5-rename-file.txt

@@ -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.

+ 14 - 0
docs/tutorial/6-conclusion.txt

@@ -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!

+ 5 - 1
docs/tutorial/index.txt

@@ -6,4 +6,8 @@ Dulwich Tutorial
 
 
 .. include:: 0-introduction.txt
 .. include:: 0-introduction.txt
 .. include:: 1-initial-commit.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

+ 178 - 0
docs/tutorial/test.py

@@ -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)