summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--README.md2
-rw-r--r--doc/source/changes.rst19
-rw-r--r--doc/source/reference.rst26
-rw-r--r--doc/source/tutorial.rst457
-rw-r--r--doc/source/whatsnew.rst34
-rw-r--r--git/cmd.py1
-rw-r--r--git/diff.py4
m---------git/ext/gitdb0
-rw-r--r--git/index/typ.py3
-rw-r--r--git/objects/submodule/base.py4
-rw-r--r--git/refs/symbolic.py2
-rw-r--r--git/remote.py18
-rw-r--r--git/repo/base.py20
-rw-r--r--git/repo/fun.py2
-rw-r--r--git/test/fixtures/uncommon_branch_prefix_FETCH_HEAD6
-rw-r--r--git/test/fixtures/uncommon_branch_prefix_stderr6
-rw-r--r--git/test/lib/helper.py4
-rw-r--r--git/test/test_docs.py297
-rw-r--r--git/test/test_remote.py16
-rw-r--r--git/test/test_submodule.py24
20 files changed, 603 insertions, 342 deletions
diff --git a/README.md b/README.md
index bdc455b6..0019dc17 100644
--- a/README.md
+++ b/README.md
@@ -50,7 +50,7 @@ Then run:
GitPython's git repo is available on GitHub, which can be browsed at [github](https://github.com/gitpython-developers/GitPython) and cloned like that:
- git clone git://github.com/gitpython-developers/GitPython.git git-python
+ git clone https://github.com/gitpython-developers/GitPython
### Live Coding
diff --git a/doc/source/changes.rst b/doc/source/changes.rst
index 2da3dad2..5b85e56a 100644
--- a/doc/source/changes.rst
+++ b/doc/source/changes.rst
@@ -4,19 +4,22 @@ Changelog
0.3.6 - Features
================
-* Added `Repo.merge_base()` implementation. See the `respective issue on github <https://github.com/gitpython-developers/GitPython/issues/169>`_
-* `[include]` sections in git configuration files are now respected
-* Added `GitConfigParser.rename_section()`
-* DOCS: special members like `__init__` are now listed in the API documentation
-* DOCS: tutorial section was revised entirely
-* Added `Submodule.rename()`
+* **DOCS**
+
+ * special members like `__init__` are now listed in the API documentation
+ * tutorial section was revised entirely, more advanced examples were added.
+
* **POSSIBLY BREAKING CHANGES**
* As `rev_parse` will now throw `BadName` as well as `BadObject`, client code will have to catch both exception types.
* Repo.working_tree_dir now returns None if it is bare. Previously it raised AssertionError.
* IndexFile.add() previously raised AssertionError when paths where used with bare repository, now it raises InvalidGitRepositoryError
-
-* A list of all issues can be found here: https://github.com/gitpython-developers/GitPython/issues?q=milestone%3A%22v0.3.6+-+Features%22+
+
+* Added `Repo.merge_base()` implementation. See the `respective issue on github <https://github.com/gitpython-developers/GitPython/issues/169>`_
+* `[include]` sections in git configuration files are now respected
+* Added `GitConfigParser.rename_section()`
+* Added `Submodule.rename()`
+* A list of all issues can be found `on github <https://github.com/gitpython-developers/GitPython/issues?q=milestone%3A%22v0.3.6+-+Features%22+>`_
0.3.5 - Bugfixes
================
diff --git a/doc/source/reference.rst b/doc/source/reference.rst
index 7a73fc71..53fa8636 100644
--- a/doc/source/reference.rst
+++ b/doc/source/reference.rst
@@ -33,6 +33,7 @@ Objects.Tag
.. automodule:: git.objects.tag
:members:
:undoc-members:
+ :special-members:
Objects.Tree
------------
@@ -40,6 +41,7 @@ Objects.Tree
.. automodule:: git.objects.tree
:members:
:undoc-members:
+ :special-members:
Objects.Functions
-----------------
@@ -47,6 +49,7 @@ Objects.Functions
.. automodule:: git.objects.fun
:members:
:undoc-members:
+ :special-members:
Objects.Submodule.base
----------------------
@@ -54,6 +57,7 @@ Objects.Submodule.base
.. automodule:: git.objects.submodule.base
:members:
:undoc-members:
+ :special-members:
Objects.Submodule.root
----------------------
@@ -61,6 +65,7 @@ Objects.Submodule.root
.. automodule:: git.objects.submodule.root
:members:
:undoc-members:
+ :special-members:
Objects.Submodule.util
----------------------
@@ -68,6 +73,7 @@ Objects.Submodule.util
.. automodule:: git.objects.submodule.util
:members:
:undoc-members:
+ :special-members:
Objects.Util
-------------
@@ -75,6 +81,7 @@ Objects.Util
.. automodule:: git.objects.util
:members:
:undoc-members:
+ :special-members:
Index.Base
----------
@@ -82,6 +89,7 @@ Index.Base
.. automodule:: git.index.base
:members:
:undoc-members:
+ :special-members:
Index.Functions
---------------
@@ -89,6 +97,7 @@ Index.Functions
.. automodule:: git.index.fun
:members:
:undoc-members:
+ :special-members:
Index.Types
-----------
@@ -96,6 +105,7 @@ Index.Types
.. automodule:: git.index.typ
:members:
:undoc-members:
+ :special-members:
Index.Util
-------------
@@ -103,6 +113,7 @@ Index.Util
.. automodule:: git.index.util
:members:
:undoc-members:
+ :special-members:
GitCmd
------
@@ -110,7 +121,7 @@ GitCmd
.. automodule:: git.cmd
:members:
:undoc-members:
-
+ :special-members:
Config
------
@@ -118,6 +129,7 @@ Config
.. automodule:: git.config
:members:
:undoc-members:
+ :special-members:
Diff
----
@@ -125,6 +137,7 @@ Diff
.. automodule:: git.diff
:members:
:undoc-members:
+ :special-members:
Exceptions
----------
@@ -132,6 +145,7 @@ Exceptions
.. automodule:: git.exc
:members:
:undoc-members:
+ :special-members:
Refs.symbolic
@@ -140,6 +154,7 @@ Refs.symbolic
.. automodule:: git.refs.symbolic
:members:
:undoc-members:
+ :special-members:
Refs.reference
--------------
@@ -147,6 +162,7 @@ Refs.reference
.. automodule:: git.refs.reference
:members:
:undoc-members:
+ :special-members:
Refs.head
---------
@@ -154,6 +170,7 @@ Refs.head
.. automodule:: git.refs.head
:members:
:undoc-members:
+ :special-members:
Refs.tag
------------
@@ -161,6 +178,7 @@ Refs.tag
.. automodule:: git.refs.tag
:members:
:undoc-members:
+ :special-members:
Refs.remote
------------
@@ -168,6 +186,7 @@ Refs.remote
.. automodule:: git.refs.remote
:members:
:undoc-members:
+ :special-members:
Refs.log
------------
@@ -175,6 +194,7 @@ Refs.log
.. automodule:: git.refs.log
:members:
:undoc-members:
+ :special-members:
Remote
------
@@ -182,6 +202,7 @@ Remote
.. automodule:: git.remote
:members:
:undoc-members:
+ :special-members:
Repo.Base
---------
@@ -189,6 +210,7 @@ Repo.Base
.. automodule:: git.repo.base
:members:
:undoc-members:
+ :special-members:
Repo.Functions
--------------
@@ -196,6 +218,7 @@ Repo.Functions
.. automodule:: git.repo.fun
:members:
:undoc-members:
+ :special-members:
Util
----
@@ -203,3 +226,4 @@ Util
.. automodule:: git.util
:members:
:undoc-members:
+ :special-members:
diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst
index 33a0a884..7f57ec94 100644
--- a/doc/source/tutorial.rst
+++ b/doc/source/tutorial.rst
@@ -8,7 +8,9 @@
GitPython Tutorial
==================
-GitPython provides object model access to your git repository. This tutorial is composed of multiple sections, each of which explains a real-life usecase.
+GitPython provides object model access to your git repository. This tutorial is composed of multiple sections, most of which explains a real-life usecase.
+
+All code presented here originated from `test_docs.py <https://github.com/gitpython-developers/GitPython/blob/master/git/test/test_docs.py>`_ to assure correctness. Knowing this should also allow you to more easily run the code for your own testing purposes, all you need is a developer installation of git-python.
Meet the Repo type
******************
@@ -17,7 +19,7 @@ The first step is to create a :class:`git.Repo <git.repo.base.Repo>` object to r
.. literalinclude:: ../../git/test/test_docs.py
:language: python
- :start-after: def test_init_repo_object
+ :start-after: # [1-test_init_repo_object]
:end-before: # ![1-test_init_repo_object]
In the above example, the directory ``self.rorepo.working_tree_dir`` equals ``/Users/mtrier/Development/git-python`` and is my working repository which contains the ``.git`` directory. You can also initialize GitPython with a *bare* repository.
@@ -58,7 +60,7 @@ Archive the repository contents to a tar file.
Advanced Repo Usage
===================
-And of course, there is much more you can do with this type, most of the following will be explained in greater detail in specific tutorials.
+And of course, there is much more you can do with this type, most of the following will be explained in greater detail in specific tutorials. Don't worry if you don't understand some of these examples right away, as they may require a thorough understanding of gits inner workings.
Query relevant repository paths ...
@@ -120,359 +122,287 @@ The :class:`index <git.index.base.IndexFile>` is also called stage in git-speak.
Examining References
********************
-References are the tips of your commit graph from which you can easily examine the history of your project::
+:class:`References <git.refs.reference.Reference>` are the tips of your commit graph from which you can easily examine the history of your project.
- heads = repo.heads
- master = heads.master # lists can be accessed by name for convenience
- master.commit # the commit pointed to by head called master
- master.rename("new_name") # rename heads
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [1-test_references_and_objects]
+ :end-before: # ![1-test_references_and_objects]
-Tags are (usually immutable) references to a commit and/or a tag object::
-
- tags = repo.tags
- tagref = tags[0]
- tagref.tag # tags may have tag objects carrying additional information
- tagref.commit # but they always point to commits
- repo.delete_tag(tagref) # delete or
- repo.create_tag("my_tag") # create tags using the repo for convenience
+:class:`Tags <git.refs.tag.TagReference>` are (usually immutable) references to a commit and/or a tag object.
+
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [2-test_references_and_objects]
+ :end-before: # ![2-test_references_and_objects]
-A symbolic reference is a special case of a reference as it points to another reference instead of a commit::
+A :class:`symbolic reference <git.refs.symbolic.SymbolicReference>` is a special case of a reference as it points to another reference instead of a commit.
- head = repo.head # the head points to the active branch/ref
- master = head.reference # retrieve the reference the head points to
- master.commit # from here you use it as any other reference
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [3-test_references_and_objects]
+ :end-before: # ![3-test_references_and_objects]
-Access the reflog easily::
+Access the :class:`reflog <git.refs.log.RefLog>` easily.
- log = master.log()
- log[0] # first (i.e. oldest) reflog entry
- log[-1] # last (i.e. most recent) reflog entry
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [4-test_references_and_objects]
+ :end-before: # ![4-test_references_and_objects]
-For more information on the reflog, see the :class:`git.RefLog <git.refs.log.RefLog>` type's documentation.
-
Modifying References
********************
-You can easily create and delete reference types or modify where they point to::
+You can easily create and delete :class:`reference types <git.refs.reference.Reference>` or modify where they point to.
- repo.delete_head('master') # delete an existing head
- master = repo.create_head('master') # create a new one
- master.commit = 'HEAD~10' # set branch to another commit without changing index or working tree
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [5-test_references_and_objects]
+ :end-before: # ![5-test_references_and_objects]
-Create or delete tags the same way except you may not change them afterwards::
+Create or delete :class:`tags <git.refs.tag.TagReference>` the same way except you may not change them afterwards.
- new_tag = repo.create_tag('my_tag', 'my message')
- repo.delete_tag(new_tag)
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [6-test_references_and_objects]
+ :end-before: # ![6-test_references_and_objects]
-Change the symbolic reference to switch branches cheaply ( without adjusting the index or the working copy )::
+Change the :class:`symbolic reference <git.refs.symbolic.SymbolicReference>` to switch branches cheaply (without adjusting the index or the working tree).
- new_branch = repo.create_head('new_branch')
- repo.head.reference = new_branch
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [7-test_references_and_objects]
+ :end-before: # ![7-test_references_and_objects]
Understanding Objects
*********************
-An Object is anything storable in git's object database. Objects contain information about their type, their uncompressed size as well as the actual data. Each object is uniquely identified by a binary SHA1 hash, being 20 bytes in size.
+An Object is anything storable in git's object database. Objects contain information about their type, their uncompressed size as well as the actual data. Each object is uniquely identified by a binary SHA1 hash, being 20 bytes in size, or 40 bytes in hexadecimal notation.
-Git only knows 4 distinct object types being Blobs, Trees, Commits and Tags.
+Git only knows 4 distinct object types being :class:`Blobs <git.objects.blob.Blob>`, :class:`Trees <git.objects.tree.Tree>`, :class:`Commits <git.objects.commit.Commit>` and :class:`Tags <git.objects.tag.TagObject>`.
-In Git-Python, all objects can be accessed through their common base, compared and hashed. They are usually not instantiated directly, but through references or specialized repository functions::
+In GitPython, all objects can be accessed through their common base, can be compared and hashed. They are usually not instantiated directly, but through references or specialized repository functions.
- hc = repo.head.commit
- hct = hc.tree
- hc != hct
- hc != repo.tags[0]
- hc == repo.head.reference.commit
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [8-test_references_and_objects]
+ :end-before: # ![8-test_references_and_objects]
-Common fields are::
-
- hct.type
- 'tree'
- hct.size
- 166
- hct.hexsha
- 'a95eeb2a7082212c197cabbf2539185ec74ed0e8'
- hct.binsha
- 'binary 20 byte sha1'
+Common fields are ...
+
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [9-test_references_and_objects]
+ :end-before: # ![9-test_references_and_objects]
-Index Objects are objects that can be put into git's index. These objects are trees, blobs and submodules which additionally know about their path in the filesystem as well as their mode::
+:class:`Index objects <git.objects.base.IndexObject>` are objects that can be put into git's index. These objects are trees, blobs and submodules which additionally know about their path in the file system as well as their mode.
- hct.path # root tree has no path
- ''
- hct.trees[0].path # the first subdirectory has one though
- 'dir'
- htc.mode # trees have the mode of a linux directory
- 040000
- '%o' % htc.blobs[0].mode # blobs have a specific mode though comparable to a standard linux fs
- 100644
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [10-test_references_and_objects]
+ :end-before: # ![10-test_references_and_objects]
-Access blob data (or any object data) directly or using streams::
+Access :class:`blob <git.objects.blob.Blob>` data (or any object data) using streams.
- htc.blobs[0].data_stream.read() # stream object to read data from
- htc.blobs[0].stream_data(open("blob_data", "w")) # write data to given stream
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [11-test_references_and_objects]
+ :end-before: # ![11-test_references_and_objects]
The Commit object
*****************
-Commit objects contain information about a specific commit. Obtain commits using references as done in `Examining References`_ or as follows.
-
-Obtain commits at the specified revision::
-
- repo.commit('master')
- repo.commit('v0.1')
- repo.commit('HEAD~10')
-
-Iterate 100 commits::
-
- repo.iter_commits('master', max_count=100)
+:class:`Commit <git.objects.commit.Commit>` objects contain information about a specific commit. Obtain commits using references as done in `Examining References`_ or as follows.
-If you need paging, you can specify a number of commits to skip::
+Obtain commits at the specified revision
- repo.iter_commits('master', max_count=10, skip=20)
-
-The above will return commits 21-30 from the commit list.::
-
- headcommit = repo.head.commit
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [12-test_references_and_objects]
+ :end-before: # ![12-test_references_and_objects]
- headcommit.hexsha
- '207c0c4418115df0d30820ab1a9acd2ea4bf4431'
+Iterate 50 commits, and if you need paging, you can specify a number of commits to skip.
- headcommit.parents
- (<git.Commit "a91c45eee0b41bf3cdaad3418ca3850664c4a4b4">,)
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [13-test_references_and_objects]
+ :end-before: # ![13-test_references_and_objects]
- headcommit.tree
- <git.Tree "563413aedbeda425d8d9dcbb744247d0c3e8a0ac">
+A commit object carries all sorts of meta-data
- headcommit.author
- <git.Actor "Michael Trier <mtrier@gmail.com>">
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [14-test_references_and_objects]
+ :end-before: # ![14-test_references_and_objects]
- headcommit.authored_date # seconds since epoch
- 1256291446
+Note: date time is represented in a ``seconds since epoch`` format. Conversion to human readable form can be accomplished with the various `time module <http://docs.python.org/library/time.html>`_ methods.
- headcommit.committer
- <git.Actor "Michael Trier <mtrier@gmail.com>">
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [15-test_references_and_objects]
+ :end-before: # ![15-test_references_and_objects]
- headcommit.committed_date
- 1256291446
+You can traverse a commit's ancestry by chaining calls to ``parents``
- headcommit.message
- 'cleaned up a lot of test information. Fixed escaping so it works with
- subprocess.'
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [16-test_references_and_objects]
+ :end-before: # ![16-test_references_and_objects]
-Note: date time is represented in a ``seconds since epoch`` format. Conversion to human readable form can be accomplished with the various `time module <http://docs.python.org/library/time.html>`_ methods::
+The above corresponds to ``master^^^`` or ``master~3`` in git parlance.
- import time
- time.asctime(time.gmtime(headcommit.committed_date))
- 'Wed May 7 05:56:02 2008'
+The Tree object
+***************
- time.strftime("%a, %d %b %Y %H:%M", time.gmtime(headcommit.committed_date))
- 'Wed, 7 May 2008 05:56'
+A :class:`tree <git.objects.tree.Tree>` records pointers to the contents of a directory. Let's say you want the root tree of the latest commit on the master branch
-You can traverse a commit's ancestry by chaining calls to ``parents``::
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [17-test_references_and_objects]
+ :end-before: # ![17-test_references_and_objects]
- headcommit.parents[0].parents[0].parents[0]
+Once you have a tree, you can get it's contents
-The above corresponds to ``master^^^`` or ``master~3`` in git parlance.
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [18-test_references_and_objects]
+ :end-before: # ![18-test_references_and_objects]
-The Tree object
-***************
+It is useful to know that a tree behaves like a list with the ability to query entries by name
-A tree records pointers to the contents of a directory. Let's say you want the root tree of the latest commit on the master branch::
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [19-test_references_and_objects]
+ :end-before: # ![19-test_references_and_objects]
- tree = repo.heads.master.commit.tree
- <git.Tree "a006b5b1a8115185a228b7514cdcd46fed90dc92">
+There is a convenience method that allows you to get a named sub-object from a tree with a syntax similar to how paths are written in a posix system
- tree.hexsha
- 'a006b5b1a8115185a228b7514cdcd46fed90dc92'
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [20-test_references_and_objects]
+ :end-before: # ![20-test_references_and_objects]
-Once you have a tree, you can get the contents::
+You can also get a commit's root tree directly from the repository
- tree.trees # trees are subdirectories
- [<git.Tree "f7eb5df2e465ab621b1db3f5714850d6732cfed2">]
-
- tree.blobs # blobs are files
- [<git.Blob "a871e79d59cf8488cac4af0c8f990b7a989e2b53">,
- <git.Blob "3594e94c04db171e2767224db355f514b13715c5">,
- <git.Blob "e79b05161e4836e5fbf197aeb52515753e8d6ab6">,
- <git.Blob "94954abda49de8615a048f8d2e64b5de848e27a1">]
-
-Its useful to know that a tree behaves like a list with the ability to query entries by name::
-
- tree[0] == tree['dir'] # access by index and by sub-path
- <git.Tree "f7eb5df2e465ab621b1db3f5714850d6732cfed2">
- for entry in tree: do_something_with(entry)
-
- blob = tree[0][0]
- blob.name
- 'file'
- blob.path
- 'dir/file'
- blob.abspath
- '/Users/mtrier/Development/git-python/dir/file'
- >>>tree['dir/file'].binsha == blob.binsha
-
-There is a convenience method that allows you to get a named sub-object from a tree with a syntax similar to how paths are written in an unix system::
-
- tree/"lib"
- <git.Tree "c1c7214dde86f76bc3e18806ac1f47c38b2b7a30">
- tree/"dir/file" == blob
-
-You can also get a tree directly from the repository if you know its name::
-
- repo.tree()
- <git.Tree "master">
-
- repo.tree("c1c7214dde86f76bc3e18806ac1f47c38b2b7a30")
- <git.Tree "c1c7214dde86f76bc3e18806ac1f47c38b2b7a30">
- repo.tree('0.1.6')
- <git.Tree "6825a94104164d9f0f5632607bebd2a32a3579e5">
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [21-test_references_and_objects]
+ :end-before: # ![21-test_references_and_objects]
-As trees only allow direct access to their direct entries, use the traverse method to obtain an iterator to traverse entries recursively::
+As trees allow direct access to their intermediate child entries only, use the traverse method to obtain an iterator to retrieve entries recursively
- tree.traverse()
- <generator object at 0x7f6598bd65a8>
- for entry in tree.traverse(): do_something_with(entry)
-
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [22-test_references_and_objects]
+ :end-before: # ![22-test_references_and_objects]
-.. note:: If tree's return Submodule objects, they will assume that they exist at the current head's commit. The tree it originated from may be rooted at another commit though, which has to be told to the Submodule object using its ``set_parent_commit(my_commit)`` method.
-
+.. note:: If trees return Submodule objects, they will assume that they exist at the current head's commit. The tree it originated from may be rooted at another commit though, that it doesn't know. That is why the caller would have to set the submodule's owning or parent commit using the ``set_parent_commit(my_commit)`` method.
The Index Object
****************
-The git index is the stage containing changes to be written with the next commit or where merges finally have to take place. You may freely access and manipulate this information using the IndexFile Object::
-
- index = repo.index
+The git index is the stage containing changes to be written with the next commit or where merges finally have to take place. You may freely access and manipulate this information using the :class:`IndexFile <git.index.base.IndexFile>` object.
+Modify the index with ease
-Access objects and add/remove entries. Commit the changes::
-
- for stage, blob in index.iter_blobs(): do_something(...)
- # Access blob objects
- for (path, stage), entry in index.entries.iteritems: pass
- # Access the entries directly
- index.add(['my_new_file']) # add a new file to the index
- index.remove(['dir/existing_file'])
- new_commit = index.commit("my commit message") # commit by commit message first
- my_author = Actor("An author", "author@example.com")
- my_committer = Actor("A committer", "committer@example.com")
- next_commit = index.commit("my commit message", author=my_author, commiter=my_committer) # commit by commit message and author and committer
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [23-test_references_and_objects]
+ :end-before: # ![23-test_references_and_objects]
-Create new indices from other trees or as result of a merge. Write that result to a new index file::
+Create new indices from other trees or as result of a merge. Write that result to a new index file for later inspection.
- tmp_index = Index.from_tree(repo, 'HEAD~1') # load a tree into a temporary index
- merge_index = Index.from_tree(repo, 'base', 'HEAD', 'some_branch') # merge two trees three-way
- merge_index.write("merged_index")
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [24-test_references_and_objects]
+ :end-before: # ![24-test_references_and_objects]
Handling Remotes
****************
-Remotes are used as alias for a foreign repository to ease pushing to and fetching from them::
+:class:`Remotes <git.remote.Remote>` are used as alias for a foreign repository to ease pushing to and fetching from them
- test_remote = repo.create_remote('test', 'git@server:repo.git')
- repo.delete_remote(test_remote) # create and delete remotes
- origin = repo.remotes.origin # get default remote by name
- origin.refs # local remote references
- o = origin.rename('new_origin') # rename remotes
- o.fetch() # fetch, pull and push from and to the remote
- o.pull()
- o.push()
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [25-test_references_and_objects]
+ :end-before: # ![25-test_references_and_objects]
-You can easily access configuration information for a remote by accessing options as if they where attributes::
+You can easily access configuration information for a remote by accessing options as if they where attributes. The modification of remote configuration is more explicit though.
- o.url
- 'git@server:dummy_repo.git'
-
-Change configuration for a specific remote only::
-
- o.config_writer.set("pushurl", "other_url")
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [26-test_references_and_objects]
+ :end-before: # ![26-test_references_and_objects]
-You can also specify an SSH key to use for any operations on the remotes::
+You can also specify an SSH key to use for any operations on the remotes
- private_key_file = project_dir+'id_rsa_deployment_key'
- with repo.git.sshkey(private_key_file):
- o.fetch()
-
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [32-test_references_and_objects]
+ :end-before: # ![32-test_references_and_objects]
Submodule Handling
******************
-Submodules can be conveniently handled using the methods provided by Git-Python, and as an added benefit, Git-Python provides functionality which behave smarter and less error prone than its original c-git implementation, that is Git-Python tries hard to keep your repository consistent when updating submodules recursively or adjusting the existing configuration.
+:class:`Submodules <git.objects.submodule.base.Submodule>` can be conveniently handled using the methods provided by GitPython, and as an added benefit, GitPython provides functionality which behave smarter and less error prone than its original c-git implementation, that is GitPython tries hard to keep your repository consistent when updating submodules recursively or adjusting the existing configuration.
-In the following brief example, you will learn about the very basics, assuming you operate on the Git-Python repository itself::
-
- >>> repo = Repo('path/to/git-python/repository')
- >>> sms = repo.submodules
- [git.Submodule(name=gitdb, path=lib/git/ext/gitdb, url=git://github.com/gitpython-developers/GitPython.git, branch=master)]
- >>> sm = sms[0]
- >>> sm.name
- 'gitdb'
- >>> sm.module() # The module is the actual repository referenced by the submodule
- <git.Repo "<prefix>/git-python/lib/git/ext/gitdb/.git">
- >>> sm.module_exists()
- True
- >>> sm.abspath == sm.module().working_tree_dir # the submodule's absolute path is the module's path
- True
- >>> sm.hexsha # Its sha defines the commit to checkout
- '2ddc5bad224d8f545ef3bb2ab3df98dfe063c5b6'
- >>> sm.exists() # yes, this submodule is valid and exists
- True
- >>> sm.config_reader().get_value('path') == sm.path # read its configuration conveniently
- True
- >>> sm.children() # query the submodule hierarchy
- [git.Submodule(name=async, path=ext/async, url=git://github.com/gitpython-developers/async.git, branch=master)]
-
-In addition to the query functionality, you can move the submodule's repository to a different path <``move(...)``>, write its configuration <``config_writer().set_value(...)``>, update its working tree <``update(...)``>, and remove and add them <``remove(...)``, ``add(...)``>.
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [1-test_submodules]
+ :end-before: # ![1-test_submodules]
+
+In addition to the query functionality, you can move the submodule's repository to a different path <``move(...)``>, write its configuration <``config_writer().set_value(...).release()``>, update its working tree <``update(...)``>, and remove or add them <``remove(...)``, ``add(...)``>.
If you obtained your submodule object by traversing a tree object which is not rooted at the head's commit, you have to inform the submodule about its actual commit to retrieve the data from by using the ``set_parent_commit(...)`` method.
-The special ``RootModule`` type allows you to treat your master repository as root of a hierarchy of submodules, which allows very convenient submodule handling. Its ``update(...)`` method is reimplemented to provide an advanced way of updating submodules as they change their values. The update method will track changes and make sure your working tree and submodule checkouts stay consistent, which is very useful in case submodules get deleted or added to name just two of the handled cases.
+The special :class:`RootModule <git.objects.submodule.root.RootModule>` type allows you to treat your master repository as root of a hierarchy of submodules, which allows very convenient submodule handling. Its ``update(...)`` method is reimplemented to provide an advanced way of updating submodules as they change their values over time. The update method will track changes and make sure your working tree and submodule checkouts stay consistent, which is very useful in case submodules get deleted or added to name just two of the handled cases.
-Additionally, Git-Python adds functionality to track a specific branch, instead of just a commit. Supported by customized update methods, you are able to automatically update submodules to the latest revision available in the remote repository, as well as to keep track of changes and movements of these submodules. To use it, set the name of the branch you want to track to the ``submodule.$name.branch`` option of the *.gitmodules* file, and use Git-Python update methods on the resulting repository with the ``to_latest_revision`` parameter turned on. In the latter case, the sha of your submodule will be ignored, instead a local tracking branch will be updated to the respective remote branch automatically. The resulting behaviour is much like the one of svn::externals, which can be useful in times.
+Additionally, GitPython adds functionality to track a specific branch, instead of just a commit. Supported by customized update methods, you are able to automatically update submodules to the latest revision available in the remote repository, as well as to keep track of changes and movements of these submodules. To use it, set the name of the branch you want to track to the ``submodule.$name.branch`` option of the *.gitmodules* file, and use GitPython update methods on the resulting repository with the ``to_latest_revision`` parameter turned on. In the latter case, the sha of your submodule will be ignored, instead a local tracking branch will be updated to the respective remote branch automatically, provided there are no local changes. The resulting behaviour is much like the one of svn::externals, which can be useful in times.
Obtaining Diff Information
**************************
-Diffs can generally be obtained by subclasses of ``Diffable`` as they provide the ``diff`` method. This operation yields a DiffIndex allowing you to easily access diff information about paths.
+Diffs can generally be obtained by subclasses of :class:`Diffable <git.diff.Diffable>` as they provide the ``diff`` method. This operation yields a :class:`DiffIndex <git.diff.DiffIndex>` allowing you to easily access diff information about paths.
-Diffs can be made between the Index and Trees, Index and the working tree, trees and trees as well as trees and the working copy. If commits are involved, their tree will be used implicitly::
+Diffs can be made between the Index and Trees, Index and the working tree, trees and trees as well as trees and the working copy. If commits are involved, their tree will be used implicitly.
- hcommit = repo.head.commit
- idiff = hcommit.diff() # diff tree against index
- tdiff = hcommit.diff('HEAD~1') # diff tree against previous tree
- wdiff = hcommit.diff(None) # diff tree against working tree
-
- index = repo.index
- index.diff() # diff index against itself yielding empty diff
- index.diff(None) # diff index against working copy
- index.diff('HEAD') # diff index against current HEAD tree
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [27-test_references_and_objects]
+ :end-before: # ![27-test_references_and_objects]
-The item returned is a DiffIndex which is essentially a list of Diff objects. It provides additional filtering to ease finding what you might be looking for::
+The item returned is a DiffIndex which is essentially a list of Diff objects. It provides additional filtering to ease finding what you might be looking for.
- for diff_added in wdiff.iter_change_type('A'): do_something_with(diff_added)
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [28-test_references_and_objects]
+ :end-before: # ![28-test_references_and_objects]
-Use the diff framework if you want to implement git-status like functionality.
+Use the diff framework if you want to implement git-status like functionality.
* A diff between the index and the commit's tree your HEAD points to
- * use repo.index.diff(repo.head)
+ * use ``repo.index.diff(repo.head)``
* A diff between the index and the working tree
- * use repo.index.diff(None)
+ * use ``repo.index.diff(None)``
* A list of untracked files
- * use repo.untracked_files
+ * use ``repo.untracked_files``
-
Switching Branches
******************
-To switch between branches, you effectively need to point your HEAD to the new branch head and reset your index and working copy to match. A simple manual way to do it is the following one::
+To switch between branches similar to ``git checkout``, you effectively need to point your HEAD symbolic reference to the new branch and reset your index and working copy to match. A simple manual way to do it is the following one
- repo.head.reference = repo.heads.other_branch
- repo.head.reset(index=True, working_tree=True)
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [29-test_references_and_objects]
+ :end-before: # ![29-test_references_and_objects]
-The previous approach would brutally overwrite the user's changes in the working copy and index though and is less sophisticated than a git-checkout for instance which generally prevents you from destroying your work. Use the safer approach as follows::
+The previous approach would brutally overwrite the user's changes in the working copy and index though and is less sophisticated than a ``git-checkout``. The latter will generally prevent you from destroying your work. Use the safer approach as follows.
- repo.heads.master.checkout() # checkout the branch using git-checkout
- repo.heads.other_branch.checkout()
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [30-test_references_and_objects]
+ :end-before: # ![30-test_references_and_objects]
Initializing a repository
*************************
@@ -488,18 +418,19 @@ Please have a look at the individual methods as they usually support a vast amou
Using git directly
******************
-In case you are missing functionality as it has not been wrapped, you may conveniently use the git command directly. It is owned by each repository instance::
+In case you are missing functionality as it has not been wrapped, you may conveniently use the :class:`git <git.cmd.Git>` command directly. It is owned by each repository instance.
- git = repo.git
- git.checkout('head', b="my_new_branch") # default command
- git.for_each_ref() # '-' becomes '_' when calling it
+.. literalinclude:: ../../git/test/test_docs.py
+ :language: python
+ :start-after: # [31-test_references_and_objects]
+ :end-before: # ![31-test_references_and_objects]
The return value will by default be a string of the standard output channel produced by the command.
-Keyword arguments translate to short and long keyword arguments on the commandline.
+Keyword arguments translate to short and long keyword arguments on the command-line.
The special notion ``git.command(flag=True)`` will create a flag without value like ``command --flag``.
-If ``None`` is found in the arguments, it will be dropped silently. Lists and tuples passed as arguments will be unpacked recursively to individual arguments. Objects are converted to strings using the str(...) function.
+If ``None`` is found in the arguments, it will be dropped silently. Lists and tuples passed as arguments will be unpacked recursively to individual arguments. Objects are converted to strings using the ``str(...)`` function.
Object Databases
@@ -528,8 +459,8 @@ Using environment variables, you can further adjust the behaviour of the git com
* **GIT_PYTHON_TRACE**
- * If set to non-0, all executed git commands will be printed to stdout.
- * if set to *full*, the executed git command will be printed along with its output.
+ * If set to non-0, all executed git commands will be logged using a python logger.
+ * if set to *full*, the executed git command and its output on stdout and stderr will be logged using a python logger.
* **GIT_PYTHON_GIT_EXECUTABLE**
@@ -538,7 +469,7 @@ Using environment variables, you can further adjust the behaviour of the git com
And even more ...
*****************
-There is more functionality in there, like the ability to archive repositories, get stats and logs, blame, and probably a few other things that were not mentioned here.
+There is more functionality in there, like the ability to archive repositories, get stats and logs, blame, and probably a few other things that were not mentioned here.
Check the unit tests for an in-depth introduction on how each function is supposed to be used.
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
index f71d88e6..e0d39b09 100644
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -23,37 +23,3 @@ Reduced Memory Footprint
Objects, such as commits, tags, trees and blobs now use 20 byte sha1 signatures internally, reducing their memory demands by 20 bytes per object, allowing you to keep more objects in memory at the same time.
The internal caches of tree objects were improved to use less memory as well.
-
-##################
-Upgrading from 0.2
-##################
-GitPython 0.2 essentially behaves like GitPython 0.3 with a Repository using the ``GitCmdObjectDB`` instead of the ``GitDB`` as object database backend. Additionally it can be used more conveniently through implicit conversions and provides a feature set strikingly similar to 0.3.
-
-**************************
-Why you should not upgrade
-**************************
-GitPython 0.3 in most cases will not run faster than GitPython 0.2, the opposite might be the case at it uses the pure python implementation by default.
-There have been a few renames which will need additional adjustments in your code.
-
-Generally, if you only read git repositories, version 0.2 is sufficient and very well performing.
-
-**********************
-Why you should upgrade
-**********************
-GitPython 0.2 has reached its end of line, and it is unlikely to receive more than contributed patches. 0.3 is the main development branch which will lead into the future.
-
-GitPython 0.3 provides memory usage optimization and is very flexible in the way it uses to access the object database. With minimal effort, 0.3 will be running as fast as 0.2. It marks the first step of more versions to come, and will improve over time.
-
-GitPython 0.3 is especially suitable for everyone who needs not only read, but also write access to a git repository. It is optimized to keep the memory consumption as low as possible, especially when handling large data sets. GitPython 0.3 operates on streams, not on possibly huge chunks of data.
-
-
-**************
-Guided Upgrade
-**************
-This guide should help to make the upgrade as painless as possible, hence it points out where to start, and what to look out for.
-
-* Have a look at https://github.com/gitpython-developers/GitPython/blob/0.3/doc/source/changes.rst
-* Start applying the renames, generally the ``utils`` modules are now called ``util``, ``errors`` is called ``exc``.
-* Search for occurrences of the ``sha`` property of object instances. A similar value can be obtained through the new ``hexsha`` property. The native sha1 value is the ``binsha`` though.
-* Search for code which instantiates objects directly. Their initializer now requires a 20 byte binary Sha1, rev-specs cannot be used anymore. For a similar effect, either convert your hexadecimal shas to binary shas beforehand ( ``binascii.unhexlify`` for instance ), or use higher level functions such as ``Object.new``, ``Repo.commit`` or ``Repo.tree``. The latter ones takes rev-specs and hexadecimal sha1 hashes.
-
diff --git a/git/cmd.py b/git/cmd.py
index a0319073..960b2a21 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -545,7 +545,6 @@ class Git(LazyMixin):
# Start the process
env = os.environ.copy()
env["LC_MESSAGES"] = "C"
- print(self._environment)
env.update(self._environment)
proc = Popen(command,
diff --git a/git/diff.py b/git/diff.py
index dfee00e2..37882369 100644
--- a/git/diff.py
+++ b/git/diff.py
@@ -42,7 +42,7 @@ class Diffable(object):
def diff(self, other=Index, paths=None, create_patch=False, **kwargs):
"""Creates diffs between two items being trees, trees and index or an
- index and the working tree.
+ index and the working tree. It will detect renames automatically.
:param other:
Is the item to compare us with.
@@ -68,8 +68,6 @@ class Diffable(object):
:return: git.DiffIndex
:note:
- Rename detection will only work if create_patch is True.
-
On a bare repository, 'other' needs to be provided as Index or as
as Tree/Commit, or a git command error will occour"""
args = list()
diff --git a/git/ext/gitdb b/git/ext/gitdb
-Subproject f2233fbf40f3f69309ce5cc714e99fcbdcd33ec
+Subproject b3237e804ae313503f5479349f90066c356b154
diff --git a/git/index/typ.py b/git/index/typ.py
index 0998ecb0..70f56dd1 100644
--- a/git/index/typ.py
+++ b/git/index/typ.py
@@ -32,7 +32,8 @@ class BlobFilter(object):
__slots__ = 'paths'
def __init__(self, paths):
- """:param paths:
+ """
+ :param paths:
tuple or list of paths which are either pointing to directories or
to files relative to the current repository
"""
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index 94322a55..be243acc 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -770,7 +770,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
an inconsistent state
:raise InvalidGitRepositoryError: thrown if the repository cannot be deleted
:raise OSError: if directories or files could not be removed"""
- if not (module + configuration):
+ if not (module or configuration):
raise ValueError("Need to specify to delete at least the module, or the configuration")
# END handle parameters
@@ -784,7 +784,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
if configuration and not dry_run and nc > 0:
# Assure we don't leave the parent repository in a dirty state, and commit our changes
# It's important for recursive, unforced, deletions to work as expected
- self.module().index.commit("Removed submodule '%s'" % self.name)
+ self.module().index.commit("Removed at least one of child-modules of '%s'" % self.name)
# end handle recursion
# DELETE REPOSITORY WORKING TREE
diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py
index b5a81ee0..fed7006e 100644
--- a/git/refs/symbolic.py
+++ b/git/refs/symbolic.py
@@ -264,7 +264,7 @@ class SymbolicReference(object):
symbolic one.
:param ref: SymbolicReference instance, Object instance or refspec string
- Only if the ref is a SymbolicRef instance, we will point to it. Everthiny
+ Only if the ref is a SymbolicRef instance, we will point to it. Everthing
else is dereferenced to obtain the actual object.
:param logmsg: If set to a string, the message will be used in the reflog.
Otherwise, a reflog entry is not written for the changed reference.
diff --git a/git/remote.py b/git/remote.py
index c176b43b..d048f87b 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -14,6 +14,7 @@ from .config import (
cp,
)
from .refs import (
+ Head,
Reference,
RemoteReference,
SymbolicReference,
@@ -177,8 +178,9 @@ class FetchInfo(object):
info.note # additional notes given by git-fetch intended for the user
info.old_commit # if info.flags & info.FORCED_UPDATE|info.FAST_FORWARD,
# field is set to the previous location of ref, otherwise None
+ info.remote_ref_path # The path from which we fetched on the remote. It's the remote's version of our info.ref
"""
- __slots__ = ('ref', 'old_commit', 'flags', 'note')
+ __slots__ = ('ref', 'old_commit', 'flags', 'note', 'remote_ref_path')
NEW_TAG, NEW_HEAD, HEAD_UPTODATE, TAG_UPDATE, REJECTED, FORCED_UPDATE, \
FAST_FORWARD, ERROR = [1 << x for x in range(8)]
@@ -193,7 +195,7 @@ class FetchInfo(object):
'=': HEAD_UPTODATE,
' ': FAST_FORWARD}
- def __init__(self, ref, flags, note='', old_commit=None):
+ def __init__(self, ref, flags, note='', old_commit=None, remote_ref_path=None):
"""
Initialize a new instance
"""
@@ -201,6 +203,7 @@ class FetchInfo(object):
self.flags = flags
self.note = note
self.old_commit = old_commit
+ self.remote_ref_path = remote_ref_path
def __str__(self):
return self.name
@@ -243,7 +246,7 @@ class FetchInfo(object):
new_hex_sha, fetch_operation, fetch_note = fetch_line.split("\t")
ref_type_name, fetch_note = fetch_note.split(' ', 1)
except ValueError: # unpack error
- raise ValueError("Failed to parse FETCH__HEAD line: %r" % fetch_line)
+ raise ValueError("Failed to parse FETCH_HEAD line: %r" % fetch_line)
# parse flags from control_character
flags = 0
@@ -288,6 +291,11 @@ class FetchInfo(object):
# note: remote-tracking is just the first part of the 'remote-tracking branch' token.
# We don't parse it correctly, but its enough to know what to do, and its new in git 1.7something
ref_type = RemoteReference
+ elif '/' in ref_type_name:
+ # If the fetch spec look something like this '+refs/pull/*:refs/heads/pull/*', and is thus pretty
+ # much anything the user wants, we will have trouble to determine what's going on
+ # For now, we assume the local ref is a Head
+ ref_type = Head
else:
raise TypeError("Cannot handle reference type: %r" % ref_type_name)
# END handle ref type
@@ -325,7 +333,7 @@ class FetchInfo(object):
note = (note and note.strip()) or ''
- return cls(remote_local_ref, flags, note, old_commit)
+ return cls(remote_local_ref, flags, note, old_commit, local_remote_ref)
class Remote(LazyMixin, Iterable):
@@ -361,7 +369,7 @@ class Remote(LazyMixin, Iterable):
def __getattr__(self, attr):
"""Allows to call this instance like
- remote.special( *args, **kwargs) to call git-remote special self.name"""
+ remote.special( \*args, \*\*kwargs) to call git-remote special self.name"""
if attr == "_config_reader":
return super(Remote, self).__getattr__(attr)
diff --git a/git/repo/base.py b/git/repo/base.py
index ce8db7f7..61352a9a 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -103,17 +103,23 @@ class Repo(object):
# represents the configuration level of a configuration file
config_level = ("system", "user", "global", "repository")
+ # Subclass configuration
+ # Subclasses may easily bring in their own custom types by placing a constructor or type here
+ GitCommandWrapperType = Git
+
def __init__(self, path=None, odbt=DefaultDBType, search_parent_directories=False):
"""Create a new Repo instance
- :param path: is the path to either the root git directory or the bare git repo::
+ :param path:
+ the path to either the root git directory or the bare git repo::
- repo = Repo("/Users/mtrier/Development/git-python")
- repo = Repo("/Users/mtrier/Development/git-python.git")
- repo = Repo("~/Development/git-python.git")
- repo = Repo("$REPOSITORIES/Development/git-python.git")
+ repo = Repo("/Users/mtrier/Development/git-python")
+ repo = Repo("/Users/mtrier/Development/git-python.git")
+ repo = Repo("~/Development/git-python.git")
+ repo = Repo("$REPOSITORIES/Development/git-python.git")
- :param odbt: Object DataBase type - a type which is constructed by providing
+ :param odbt:
+ Object DataBase type - a type which is constructed by providing
the directory containing the database objects, i.e. .git/objects. It will
be used to access all object data
:raise InvalidGitRepositoryError:
@@ -170,7 +176,7 @@ class Repo(object):
# END working dir handling
self.working_dir = self._working_tree_dir or self.git_dir
- self.git = Git(self.working_dir)
+ self.git = self.GitCommandWrapperType(self.working_dir)
# special handling, in special times
args = [join(self.git_dir, 'objects')]
diff --git a/git/repo/fun.py b/git/repo/fun.py
index 2321dbc8..1d551f04 100644
--- a/git/repo/fun.py
+++ b/git/repo/fun.py
@@ -148,8 +148,6 @@ def rev_parse(repo, rev):
:param rev: git-rev-parse compatible revision specification as string, please see
http://www.kernel.org/pub/software/scm/git/docs/git-rev-parse.html
for details
- :note: Currently there is no access to the rev-log, rev-specs may only contain
- topological tokens such ~ and ^.
:raise BadObject: if the given revision could not be found
:raise ValueError: If rev couldn't be parsed
:raise IndexError: If invalid reflog index is specified"""
diff --git a/git/test/fixtures/uncommon_branch_prefix_FETCH_HEAD b/git/test/fixtures/uncommon_branch_prefix_FETCH_HEAD
new file mode 100644
index 00000000..7df36f24
--- /dev/null
+++ b/git/test/fixtures/uncommon_branch_prefix_FETCH_HEAD
@@ -0,0 +1,6 @@
+c2e3c20affa3e2b61a05fdc9ee3061dd416d915e 'refs/pull/1/head' of http://github.com/loic-bot/testrepo
+fd8695d980e2c6df62b7785f93fd6292d1e283fb 'refs/pull/1/merge' of http://github.com/loic-bot/testrepo
+bb46faf089720d1a3f9e4dc3b11ed5ff77d7e764 'refs/pull/2/head' of http://github.com/loic-bot/testrepo
+5faa366d58454eceea811e0e34c502bdd7b37e4b 'refs/pull/2/merge' of http://github.com/loic-bot/testrepo
+b3ad3c4f1864b50d4d3e09320947a1a3c34c9ea2 'refs/pull/3/head' of http://github.com/loic-bot/testrepo
+71fe57e511776042b009ed4bb281b62b0522b434 'refs/pull/3/merge' of http://github.com/loic-bot/testrepo
diff --git a/git/test/fixtures/uncommon_branch_prefix_stderr b/git/test/fixtures/uncommon_branch_prefix_stderr
new file mode 100644
index 00000000..5a6aca65
--- /dev/null
+++ b/git/test/fixtures/uncommon_branch_prefix_stderr
@@ -0,0 +1,6 @@
+ = [up to date] refs/pull/1/head -> pull/1/head
+ = [up to date] refs/pull/1/merge -> pull/1/merge
+ = [up to date] refs/pull/2/head -> pull/2/head
+ = [up to date] refs/pull/2/merge -> pull/2/merge
+ = [up to date] refs/pull/3/head -> pull/3/head
+ = [up to date] refs/pull/3/merge -> pull/3/merge
diff --git a/git/test/lib/helper.py b/git/test/lib/helper.py
index c5c5a620..8300f272 100644
--- a/git/test/lib/helper.py
+++ b/git/test/lib/helper.py
@@ -275,6 +275,10 @@ class TestBase(TestCase):
of the project history ( to assure tests don't fail for others ).
"""
+ def _small_repo_url(self):
+ """:return" a path to a small, clonable repository"""
+ return os.path.join(self.rorepo.working_tree_dir, 'git/ext/gitdb/gitdb/ext/smmap')
+
@classmethod
def setUpClass(cls):
"""
diff --git a/git/test/test_docs.py b/git/test/test_docs.py
index 9a04784d..965d10fb 100644
--- a/git/test/test_docs.py
+++ b/git/test/test_docs.py
@@ -14,6 +14,7 @@ class Tutorials(TestBase):
@with_rw_directory
def test_init_repo_object(self, rw_dir):
+ # [1-test_init_repo_object]
from git import Repo
join = os.path.join
@@ -36,9 +37,8 @@ class Tutorials(TestBase):
# ![3-test_init_repo_object]
# [4-test_init_repo_object]
- repo.is_dirty()
- # False
- repo.untracked_files
+ assert not bare_repo.is_dirty() # check the dirty state
+ repo.untracked_files # retrieve a list of untracked files
# ['my_untracked_file']
# ![4-test_init_repo_object]
@@ -178,6 +178,297 @@ class Tutorials(TestBase):
# ![14-test_init_repo_object]
@with_rw_directory
+ def test_references_and_objects(self, rw_dir):
+ # [1-test_references_and_objects]
+ import git
+ repo = git.Repo.clone_from(self._small_repo_url(), os.path.join(rw_dir, 'repo'), branch='master')
+
+ heads = repo.heads
+ master = heads.master # lists can be accessed by name for convenience
+ master.commit # the commit pointed to by head called master
+ master.rename('new_name') # rename heads
+ master.rename('master')
+ # ![1-test_references_and_objects]
+
+ # [2-test_references_and_objects]
+ tags = repo.tags
+ tagref = tags[0]
+ tagref.tag # tags may have tag objects carrying additional information
+ tagref.commit # but they always point to commits
+ repo.delete_tag(tagref) # delete or
+ repo.create_tag("my_tag") # create tags using the repo for convenience
+ # ![2-test_references_and_objects]
+
+ # [3-test_references_and_objects]
+ head = repo.head # the head points to the active branch/ref
+ master = head.reference # retrieve the reference the head points to
+ master.commit # from here you use it as any other reference
+ # ![3-test_references_and_objects]
+
+ # [4-test_references_and_objects]
+ log = master.log()
+ log[0] # first (i.e. oldest) reflog entry
+ log[-1] # last (i.e. most recent) reflog entry
+ # ![4-test_references_and_objects]
+
+ # [5-test_references_and_objects]
+ new_branch = repo.create_head('new') # create a new one
+ new_branch.commit = 'HEAD~10' # set branch to another commit without changing index or working trees
+ repo.delete_head(new_branch) # delete an existing head - only works if it is not checked out
+ # ![5-test_references_and_objects]
+
+ # [6-test_references_and_objects]
+ new_tag = repo.create_tag('my_new_tag', message='my message')
+ # You cannot change the commit a tag points to. Tags need to be re-created
+ self.failUnlessRaises(AttributeError, setattr, new_tag, 'commit', repo.commit('HEAD~1'))
+ repo.delete_tag(new_tag)
+ # ![6-test_references_and_objects]
+
+ # [7-test_references_and_objects]
+ new_branch = repo.create_head('another-branch')
+ repo.head.reference = new_branch
+ # ![7-test_references_and_objects]
+
+ # [8-test_references_and_objects]
+ hc = repo.head.commit
+ hct = hc.tree
+ hc != hct
+ hc != repo.tags[0]
+ hc == repo.head.reference.commit
+ # ![8-test_references_and_objects]
+
+ # [9-test_references_and_objects]
+ assert hct.type == 'tree' # preset string type, being a class attribute
+ assert hct.size > 0 # size in bytes
+ assert len(hct.hexsha) == 40
+ assert len(hct.binsha) == 20
+ # ![9-test_references_and_objects]
+
+ # [10-test_references_and_objects]
+ assert hct.path == '' # root tree has no path
+ assert hct.trees[0].path != '' # the first contained item has one though
+ assert hct.mode == 0o40000 # trees have the mode of a linux directory
+ assert hct.blobs[0].mode == 0o100644 # blobs have a specific mode though comparable to a standard linux fs
+ # ![10-test_references_and_objects]
+
+ # [11-test_references_and_objects]
+ hct.blobs[0].data_stream.read() # stream object to read data from
+ hct.blobs[0].stream_data(open(os.path.join(rw_dir, 'blob_data'), 'wb')) # write data to given stream
+ # ![11-test_references_and_objects]
+
+ # [12-test_references_and_objects]
+ repo.commit('master')
+ repo.commit('v0.8.1')
+ repo.commit('HEAD~10')
+ # ![12-test_references_and_objects]
+
+ # [13-test_references_and_objects]
+ fifty_first_commits = list(repo.iter_commits('master', max_count=50))
+ assert len(fifty_first_commits) == 50
+ # this will return commits 21-30 from the commit list as traversed backwards master
+ ten_commits_past_twenty = list(repo.iter_commits('master', max_count=10, skip=20))
+ assert len(ten_commits_past_twenty) == 10
+ assert fifty_first_commits[20:30] == ten_commits_past_twenty
+ # ![13-test_references_and_objects]
+
+ # [14-test_references_and_objects]
+ headcommit = repo.head.commit
+ assert len(headcommit.hexsha) == 40
+ assert len(headcommit.parents) > 0
+ assert headcommit.tree.type == 'tree'
+ assert headcommit.author.name == 'Sebastian Thiel'
+ assert isinstance(headcommit.authored_date, int)
+ assert headcommit.committer.name == 'Sebastian Thiel'
+ assert isinstance(headcommit.committed_date, int)
+ assert headcommit.message != ''
+ # ![14-test_references_and_objects]
+
+ # [15-test_references_and_objects]
+ import time
+ time.asctime(time.gmtime(headcommit.committed_date))
+ time.strftime("%a, %d %b %Y %H:%M", time.gmtime(headcommit.committed_date))
+ # ![15-test_references_and_objects]
+
+ # [16-test_references_and_objects]
+ assert headcommit.parents[0].parents[0].parents[0] == repo.commit('master^^^')
+ # ![16-test_references_and_objects]
+
+ # [17-test_references_and_objects]
+ tree = repo.heads.master.commit.tree
+ assert len(tree.hexsha) == 40
+ # ![17-test_references_and_objects]
+
+ # [18-test_references_and_objects]
+ assert len(tree.trees) > 0 # trees are subdirectories
+ assert len(tree.blobs) > 0 # blobs are files
+ assert len(tree.blobs) + len(tree.trees) == len(tree)
+ # ![18-test_references_and_objects]
+
+ # [19-test_references_and_objects]
+ assert tree['smmap'] == tree / 'smmap' # access by index and by sub-path
+ for entry in tree: # intuitive iteration of tree members
+ print(entry)
+ blob = tree.trees[0].blobs[0] # let's get a blob in a sub-tree
+ assert blob.name
+ assert len(blob.path) < len(blob.abspath)
+ assert tree.trees[0].name + '/' + blob.name == blob.path # this is how the relative blob path is generated
+ assert tree[blob.path] == blob # you can use paths like 'dir/file' in tree[...]
+ # ![19-test_references_and_objects]
+
+ # [20-test_references_and_objects]
+ assert tree / 'smmap' == tree['smmap']
+ assert tree / blob.path == tree[blob.path]
+ # ![20-test_references_and_objects]
+
+ # [21-test_references_and_objects]
+ # This example shows the various types of allowed ref-specs
+ assert repo.tree() == repo.head.commit.tree
+ past = repo.commit('HEAD~5')
+ assert repo.tree(past) == repo.tree(past.hexsha)
+ assert repo.tree('v0.8.1').type == 'tree' # yes, you can provide any refspec - works everywhere
+ # ![21-test_references_and_objects]
+
+ # [22-test_references_and_objects]
+ assert len(tree) < len(list(tree.traverse()))
+ # ![22-test_references_and_objects]
+
+ # [23-test_references_and_objects]
+ index = repo.index
+ # The index contains all blobs in a flat list
+ assert len(list(index.iter_blobs())) == len([o for o in repo.head.commit.tree.traverse() if o.type == 'blob'])
+ # Access blob objects
+ for (path, stage), entry in index.entries.items():
+ pass
+ new_file_path = os.path.join(repo.working_tree_dir, 'new-file-name')
+ open(new_file_path, 'w').close()
+ index.add([new_file_path]) # add a new file to the index
+ index.remove(['LICENSE']) # remove an existing one
+ assert os.path.isfile(os.path.join(repo.working_tree_dir, 'LICENSE')) # working tree is untouched
+
+ assert index.commit("my commit message").type == 'commit' # commit changed index
+ repo.active_branch.commit = repo.commit('HEAD~1') # forget last commit
+
+ from git import Actor
+ author = Actor("An author", "author@example.com")
+ committer = Actor("A committer", "committer@example.com")
+ # commit by commit message and author and committer
+ index.commit("my commit message", author=author, committer=committer)
+ # ![23-test_references_and_objects]
+
+ # [24-test_references_and_objects]
+ from git import IndexFile
+ # loads a tree into a temporary index, which exists just in memory
+ IndexFile.from_tree(repo, 'HEAD~1')
+ # merge two trees three-way into memory
+ merge_index = IndexFile.from_tree(repo, 'HEAD~10', 'HEAD', repo.merge_base('HEAD~10', 'HEAD'))
+ # and persist it
+ merge_index.write(os.path.join(rw_dir, 'merged_index'))
+ # ![24-test_references_and_objects]
+
+ # [25-test_references_and_objects]
+ empty_repo = git.Repo.init(os.path.join(rw_dir, 'empty'))
+ origin = empty_repo.create_remote('origin', repo.remotes.origin.url)
+ assert origin.exists()
+ assert origin == empty_repo.remotes.origin == empty_repo.remotes['origin']
+ origin.fetch() # assure we actually have data. fetch() returns useful information
+ # Setup a local tracking branch of a remote branch
+ empty_repo.create_head('master', origin.refs.master).set_tracking_branch(origin.refs.master)
+ origin.rename('new_origin') # rename remotes
+ # push and pull behaves similarly to `git push|pull`
+ origin.pull()
+ origin.push()
+ # assert not empty_repo.delete_remote(origin).exists() # create and delete remotes
+ # ![25-test_references_and_objects]
+
+ # [26-test_references_and_objects]
+ assert origin.url == repo.remotes.origin.url
+ cw = origin.config_writer
+ cw.set("pushurl", "other_url")
+ cw.release()
+
+ # Please note that in python 2, writing origin.config_writer.set(...) is totally safe.
+ # In py3 __del__ calls can be delayed, thus not writing changes in time.
+ # ![26-test_references_and_objects]
+
+ # [27-test_references_and_objects]
+ hcommit = repo.head.commit
+ hcommit.diff() # diff tree against index
+ hcommit.diff('HEAD~1') # diff tree against previous tree
+ hcommit.diff(None) # diff tree against working tree
+
+ index = repo.index
+ index.diff() # diff index against itself yielding empty diff
+ index.diff(None) # diff index against working copy
+ index.diff('HEAD') # diff index against current HEAD tree
+ # ![27-test_references_and_objects]
+
+ # [28-test_references_and_objects]
+ # Traverse added Diff objects only
+ for diff_added in hcommit.diff('HEAD~1').iter_change_type('A'):
+ print(diff_added)
+ # ![28-test_references_and_objects]
+
+ # [29-test_references_and_objects]
+ # Reset our working tree 10 commits into the past
+ past_branch = repo.create_head('past_branch', 'HEAD~10')
+ repo.head.reference = past_branch
+ assert not repo.head.is_detached
+ # reset the index and working tree to match the pointed-to commit
+ repo.head.reset(index=True, working_tree=True)
+
+ # To detach your head, you have to point to a commit directy
+ repo.head.reference = repo.commit('HEAD~5')
+ assert repo.head.is_detached
+ # now our head points 15 commits into the past, whereas the working tree
+ # and index are 10 commits in the past
+ # ![29-test_references_and_objects]
+
+ # [30-test_references_and_objects]
+ # checkout the branch using git-checkout. It will fail as the working tree appears dirty
+ self.failUnlessRaises(git.GitCommandError, repo.heads.master.checkout)
+ repo.heads.past_branch.checkout()
+ # ![30-test_references_and_objects]
+
+ # [31-test_references_and_objects]
+ git = repo.git
+ git.checkout('HEAD', b="my_new_branch") # create a new branch
+ git.branch('another-new-one')
+ git.branch('-D', 'another-new-one') # pass strings for full control over argument order
+ git.for_each_ref() # '-' becomes '_' when calling it
+ # ![31-test_references_and_objects]
+
+ # [32-test_references_and_objects]
+ private_key_file = os.path.join(rw_dir, 'id_rsa_deployment_key')
+ with repo.git.sshkey(private_key_file):
+ # Note that we don't actually make the call here, as our test-setup doesn't permit it to
+ # succeed.
+ # It will in your case :)
+ repo.remotes.origin.fetch
+ # ![32-test_references_and_objects]
+
+ def test_submodules(self):
+ # [1-test_submodules]
+ repo = self.rorepo
+ sms = repo.submodules
+
+ assert len(sms) == 1
+ sm = sms[0]
+ assert sm.name == 'gitdb' # git-python has gitdb as single submodule ...
+ assert sm.children()[0].name == 'smmap' # ... which has smmap as single submodule
+
+ # The module is the repository referenced by the submodule
+ assert sm.module_exists() # the module is available, which doesn't have to be the case.
+ assert sm.module().working_tree_dir.endswith('gitdb')
+ # the submodule's absolute path is the module's path
+ assert sm.abspath == sm.module().working_tree_dir
+ assert len(sm.hexsha) == 40 # Its sha defines the commit to checkout
+ assert sm.exists() # yes, this submodule is valid and exists
+ # read its configuration conveniently
+ assert sm.config_reader().get_value('path') == sm.path
+ assert len(sm.children()) == 1 # query the submodule hierarchy
+ # ![1-test_submodules]
+
+ @with_rw_directory
def test_add_file_and_commit(self, rw_dir):
import git
diff --git a/git/test/test_remote.py b/git/test/test_remote.py
index bf2f76a8..98d74d8b 100644
--- a/git/test/test_remote.py
+++ b/git/test/test_remote.py
@@ -7,7 +7,8 @@
from git.test.lib import (
TestBase,
with_rw_repo,
- with_rw_and_rw_remote_repo
+ with_rw_and_rw_remote_repo,
+ fixture
)
from git import (
RemoteProgress,
@@ -520,3 +521,16 @@ class TestRemote(TestBase):
assert type(fi.ref) is Reference
assert fi.ref.path == "refs/something/branch"
+
+ def test_uncommon_branch_names(self):
+ stderr_lines = fixture('uncommon_branch_prefix_stderr').decode('ascii').splitlines()
+ fetch_lines = fixture('uncommon_branch_prefix_FETCH_HEAD').decode('ascii').splitlines()
+
+ # The contents of the files above must be fetched with a custom refspec:
+ # +refs/pull/*:refs/heads/pull/*
+ res = [FetchInfo._from_line('ShouldntMatterRepo', stderr, fetch_line)
+ for stderr, fetch_line in zip(stderr_lines, fetch_lines)]
+ assert len(res)
+ assert res[0].remote_ref_path == 'refs/pull/1/head'
+ assert res[0].ref.path == 'refs/heads/pull/1/head'
+ assert isinstance(res[0].ref, Head)
diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py
index 1d4cf178..459e2030 100644
--- a/git/test/test_submodule.py
+++ b/git/test/test_submodule.py
@@ -264,6 +264,15 @@ class TestSubmodule(TestBase):
########################
# must delete something
self.failUnlessRaises(ValueError, csm.remove, module=False, configuration=False)
+
+ # module() is supposed to point to gitdb, which has a child-submodule whose URL is still pointing
+ # to github. To save time, we will change it to
+ csm.set_parent_commit(csm.repo.head.commit)
+ cw = csm.config_writer()
+ cw.set_value('url', self._small_repo_url())
+ cw.release()
+ csm.repo.index.commit("adjusted URL to point to local source, instead of the internet")
+
# We have modified the configuration, hence the index is dirty, and the
# deletion will fail
# NOTE: As we did a few updates in the meanwhile, the indices were reset
@@ -651,13 +660,10 @@ class TestSubmodule(TestBase):
url=empty_repo_dir, no_checkout=checkout_mode and True or False)
# end for each checkout mode
- def _submodule_url(self):
- return os.path.join(self.rorepo.working_tree_dir, 'git/ext/gitdb/gitdb/ext/smmap')
-
@with_rw_directory
def test_git_submodules(self, rwdir):
parent = git.Repo.init(os.path.join(rwdir, 'parent'))
- parent.git.submodule('add', self._submodule_url(), 'module')
+ parent.git.submodule('add', self._small_repo_url(), 'module')
parent.index.commit("added submodule")
assert len(parent.submodules) == 1
@@ -665,7 +671,7 @@ class TestSubmodule(TestBase):
assert sm.exists() and sm.module_exists()
- clone = git.Repo.clone_from(self._submodule_url(),
+ clone = git.Repo.clone_from(self._small_repo_url(),
os.path.join(parent.working_tree_dir, 'existing-subrepository'))
sm2 = parent.create_submodule('nongit-file-submodule', clone.working_tree_dir)
assert len(parent.submodules) == 2
@@ -684,7 +690,7 @@ class TestSubmodule(TestBase):
def test_git_submodule_compatibility(self, rwdir):
parent = git.Repo.init(os.path.join(rwdir, 'parent'))
sm_path = 'submodules/intermediate/one'
- sm = parent.create_submodule('mymodules/myname', sm_path, url=self._submodule_url())
+ sm = parent.create_submodule('mymodules/myname', sm_path, url=self._small_repo_url())
parent.index.commit("added submodule")
def assert_exists(sm, value=True):
@@ -714,7 +720,7 @@ class TestSubmodule(TestBase):
# Add additional submodule level
csm = sm.module().create_submodule('nested-submodule', 'nested-submodule/working-tree',
- url=self._submodule_url())
+ url=self._small_repo_url())
sm.module().index.commit("added nested submodule")
sm_head_commit = sm.module().commit()
assert_exists(csm)
@@ -759,7 +765,7 @@ class TestSubmodule(TestBase):
def test_rename(self, rwdir):
parent = git.Repo.init(os.path.join(rwdir, 'parent'))
sm_name = 'mymodules/myname'
- sm = parent.create_submodule(sm_name, sm_name, url=self._submodule_url())
+ sm = parent.create_submodule(sm_name, sm_name, url=self._small_repo_url())
parent.index.commit("Added submodule")
assert sm.rename(sm_name) is sm and sm.name == sm_name
@@ -782,7 +788,7 @@ class TestSubmodule(TestBase):
def test_branch_renames(self, rw_dir):
# Setup initial sandbox:
# parent repo has one submodule, which has all the latest changes
- source_url = self._submodule_url()
+ source_url = self._small_repo_url()
sm_source_repo = git.Repo.clone_from(source_url, os.path.join(rw_dir, 'sm-source'), b='master')
parent_repo = git.Repo.init(os.path.join(rw_dir, 'parent'))
sm = parent_repo.create_submodule('mysubmodule', 'subdir/submodule',