summaryrefslogtreecommitdiff
path: root/git
diff options
context:
space:
mode:
authorSebastian Thiel <byronimo@gmail.com>2015-01-21 18:34:58 +0100
committerSebastian Thiel <byronimo@gmail.com>2015-01-21 18:34:58 +0100
commite4d3809161fc54d6913c0c2c7f6a7b51eebe223f (patch)
tree1eebc9f43a1302c537da84e9a7219918da131f45 /git
parente48e52001d5abad7b28a4ecadde63c78c3946339 (diff)
downloadgitpython-e4d3809161fc54d6913c0c2c7f6a7b51eebe223f.tar.gz
Added advance usage examples to tutorial and made minor fixes.
GIT_PYTHON_TRACE would actually fail (now) if we debugged archive operations. Related to #239
Diffstat (limited to 'git')
-rw-r--r--git/cmd.py9
-rw-r--r--git/objects/base.py2
-rw-r--r--git/objects/commit.py9
-rw-r--r--git/objects/submodule/base.py2
-rw-r--r--git/refs/reference.py5
-rw-r--r--git/remote.py29
-rw-r--r--git/repo/base.py6
-rw-r--r--git/test/fixtures/diff_rename4
-rw-r--r--git/test/test_diff.py6
-rw-r--r--git/test/test_docs.py142
-rw-r--r--git/test/test_remote.py4
11 files changed, 185 insertions, 33 deletions
diff --git a/git/cmd.py b/git/cmd.py
index d6b29d91..55ed74dd 100644
--- a/git/cmd.py
+++ b/git/cmd.py
@@ -579,11 +579,16 @@ class Git(LazyMixin):
if self.GIT_PYTHON_TRACE == 'full':
cmdstr = " ".join(command)
+
+ def as_text(stdout_value):
+ return not output_stream and stdout_value.decode(defenc) or '<OUTPUT_STREAM>'
+ # end
+
if stderr_value:
log.info("%s -> %d; stdout: '%s'; stderr: '%s'",
- cmdstr, status, stdout_value.decode(defenc), stderr_value.decode(defenc))
+ cmdstr, status, as_text(stdout_value), stderr_value.decode(defenc))
elif stdout_value:
- log.info("%s -> %d; stdout: '%s'", cmdstr, status, stdout_value.decode(defenc))
+ log.info("%s -> %d; stdout: '%s'", cmdstr, status, as_text(stdout_value))
else:
log.info("%s -> %d", cmdstr, status)
# END handle debug printing
diff --git a/git/objects/base.py b/git/objects/base.py
index 42876fc8..77d0ed63 100644
--- a/git/objects/base.py
+++ b/git/objects/base.py
@@ -135,7 +135,7 @@ class IndexObject(Object):
:param repo: is the Repo we are located in
:param binsha: 20 byte sha1
- :param mode:
+ :param mode:
is the stat compatible file mode as int, use the stat module
to evaluate the infomration
:param path:
diff --git a/git/objects/commit.py b/git/objects/commit.py
index f2ce91ca..b9718694 100644
--- a/git/objects/commit.py
+++ b/git/objects/commit.py
@@ -69,6 +69,7 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
message=None, parents=None, encoding=None, gpgsig=None):
"""Instantiate a new Commit. All keyword arguments taking None as default will
be implicitly set on first query.
+
:param binsha: 20 byte sha1
:param parents: tuple( Commit, ... )
is a tuple of commit ids or actual Commits
@@ -97,7 +98,8 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
dependency graph
:return: git.Commit
- :note: Timezone information is in the same format and in the same sign
+ :note:
+ Timezone information is in the same format and in the same sign
as what time.altzone returns. The sign is inverted compared to git's
UTC timezone."""
super(Commit, self).__init__(repo, binsha)
@@ -296,6 +298,11 @@ class Commit(base.Object, Iterable, Diffable, Traversable, Serializable):
# empty repositories have no head commit
parent_commits = list()
# END handle parent commits
+ else:
+ for p in parent_commits:
+ if not isinstance(p, cls):
+ raise ValueError("Parent commit '%r' must be of type %s" % (p, cls))
+ # end check parent commit types
# END if parent commits are unset
# retrieve all additional information, create a commit object, and
diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py
index ebb66495..cd7d4ec4 100644
--- a/git/objects/submodule/base.py
+++ b/git/objects/submodule/base.py
@@ -781,7 +781,7 @@ class Submodule(util.IndexObject, Iterable, Traversable):
csm.remove(module, force, configuration, dry_run)
del(csm)
# end
- if not dry_run and nc > 0:
+ 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)
diff --git a/git/refs/reference.py b/git/refs/reference.py
index 8741ebb9..3e132aef 100644
--- a/git/refs/reference.py
+++ b/git/refs/reference.py
@@ -51,7 +51,8 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
#{ Interface
def set_object(self, object, logmsg=None):
- """Special version which checks if the head-log needs an update as well"""
+ """Special version which checks if the head-log needs an update as well
+ :return: self"""
oldbinsha = None
if logmsg is not None:
head = self.repo.head
@@ -78,6 +79,8 @@ class Reference(SymbolicReference, LazyMixin, Iterable):
self.repo.head.log_append(oldbinsha, logmsg)
# END check if the head
+ return self
+
# NOTE: Don't have to overwrite properties as the will only work without a the log
@property
diff --git a/git/remote.py b/git/remote.py
index dbb82796..fcec5228 100644
--- a/git/remote.py
+++ b/git/remote.py
@@ -378,6 +378,8 @@ class Remote(LazyMixin, Iterable):
def _set_cache_(self, attr):
if attr == "_config_reader":
+ # NOTE: This is cached as __getattr__ is overridden to return remote config values implicitly, such as
+ # in print(r.pushurl)
self._config_reader = SectionConstraint(self.repo.config_reader(), self._config_section_name())
else:
super(Remote, self)._set_cache_(attr)
@@ -475,8 +477,13 @@ class Remote(LazyMixin, Iterable):
@classmethod
def remove(cls, repo, name):
- """Remove the remote with the given name"""
+ """Remove the remote with the given name
+ :return: the passed remote name to remove
+ """
repo.git.remote("rm", name)
+ if isinstance(name, cls):
+ name._clear_cache()
+ return name
# alias
rm = remove
@@ -489,11 +496,8 @@ class Remote(LazyMixin, Iterable):
self.repo.git.remote("rename", self.name, new_name)
self.name = new_name
- try:
- del(self._config_reader) # it contains cached values, section names are different now
- except AttributeError:
- pass
- # END handle exception
+ self._clear_cache()
+
return self
def update(self, **kwargs):
@@ -662,6 +666,13 @@ class Remote(LazyMixin, Iterable):
Hence you may simple type config.get("pushurl") to obtain the information"""
return self._config_reader
+ def _clear_cache(self):
+ try:
+ del(self._config_reader)
+ except AttributeError:
+ pass
+ # END handle exception
+
@property
def config_writer(self):
"""
@@ -676,9 +687,5 @@ class Remote(LazyMixin, Iterable):
writer = self.repo.config_writer()
# clear our cache to assure we re-read the possibly changed configuration
- try:
- del(self._config_reader)
- except AttributeError:
- pass
- # END handle exception
+ self._clear_cache()
return SectionConstraint(writer, self._config_section_name())
diff --git a/git/repo/base.py b/git/repo/base.py
index ef12473b..ce8db7f7 100644
--- a/git/repo/base.py
+++ b/git/repo/base.py
@@ -209,10 +209,8 @@ class Repo(object):
@property
def working_tree_dir(self):
- """:return: The working tree directory of our git repository
- :raise AssertionError: If we are a bare repository"""
- if self._working_tree_dir is None:
- raise AssertionError("Repository at %r is bare and does not have a working tree directory" % self.git_dir)
+ """:return: The working tree directory of our git repository. If this is a bare repository, None is returned.
+ """
return self._working_tree_dir
@property
diff --git a/git/test/fixtures/diff_rename b/git/test/fixtures/diff_rename
index 13abae0e..2d5241e3 100644
--- a/git/test/fixtures/diff_rename
+++ b/git/test/fixtures/diff_rename
@@ -8,5 +8,5 @@ committer Michael Trier <mtrier@gmail.com> 1229389391 -0500
diff --git a/AUTHORS b/CONTRIBUTORS
similarity index 100%
-rename from AUTHORS
-rename to CONTRIBUTORS
+rename from Jérôme
+rename to müller
diff --git a/git/test/test_diff.py b/git/test/test_diff.py
index 42972603..f2ce1447 100644
--- a/git/test/test_diff.py
+++ b/git/test/test_diff.py
@@ -1,3 +1,4 @@
+#-*-coding:utf-8-*-
# test_diff.py
# Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors
#
@@ -53,8 +54,9 @@ class TestDiff(TestBase):
diff = diffs[0]
assert_true(diff.renamed)
- assert_equal(diff.rename_from, 'AUTHORS')
- assert_equal(diff.rename_to, 'CONTRIBUTORS')
+ assert_equal(diff.rename_from, u'Jérôme')
+ assert_equal(diff.rename_to, u'müller')
+ assert isinstance(str(diff), str)
output = StringProcessAdapter(fixture('diff_rename_raw'))
diffs = Diff._index_from_raw_format(self.rorepo, output.stdout)
diff --git a/git/test/test_docs.py b/git/test/test_docs.py
index 6befb9ea..9a04784d 100644
--- a/git/test/test_docs.py
+++ b/git/test/test_docs.py
@@ -21,14 +21,14 @@ class Tutorials(TestBase):
# For all you know, the first argument to Repo is a path to the repository
# you want to work with
repo = Repo(self.rorepo.working_tree_dir)
- assert repo.bare == False
+ assert not repo.bare
# ![1-test_init_repo_object]
# [2-test_init_repo_object]
- bare_empty_repo = Repo.init(join(rw_dir, 'bare-repo'), bare=True)
- assert bare_empty_repo.bare == True
+ bare_repo = Repo.init(join(rw_dir, 'bare-repo'), bare=True)
+ assert bare_repo.bare
# ![2-test_init_repo_object]
-
+
# [3-test_init_repo_object]
repo.config_reader() # get a config reader for read-only access
cw = repo.config_writer() # get a config writer to change configuration
@@ -41,16 +41,142 @@ class Tutorials(TestBase):
repo.untracked_files
# ['my_untracked_file']
# ![4-test_init_repo_object]
-
+
# [5-test_init_repo_object]
- assert repo.clone(join(rw_dir, 'to/this/path')).__class__ is Repo
+ cloned_repo = repo.clone(join(rw_dir, 'to/this/path'))
+ assert cloned_repo.__class__ is Repo # clone an existing repository
assert Repo.init(join(rw_dir, 'path/for/new/repo')).__class__ is Repo
# ![5-test_init_repo_object]
-
+
# [6-test_init_repo_object]
- repo.archive(open(join(rw_dir, 'repo.tar'), 'w'))
+ repo.archive(open(join(rw_dir, 'repo.tar'), 'wb'))
# ![6-test_init_repo_object]
+ # repository paths
+ # [7-test_init_repo_object]
+ assert os.path.isdir(cloned_repo.working_tree_dir) # directory with your work files
+ assert cloned_repo.git_dir.startswith(cloned_repo.working_tree_dir) # directory containing the git repository
+ assert bare_repo.working_tree_dir is None # bare repositories have no working tree
+ # ![7-test_init_repo_object]
+
+ # heads, tags and references
+ # heads are branches in git-speak
+ # [8-test_init_repo_object]
+ assert repo.head.ref == repo.heads.master # head is a symbolic reference pointing to master
+ assert repo.tags['0.3.5'] == repo.tag('refs/tags/0.3.5') # you can access tags in various ways too
+ assert repo.refs.master == repo.heads['master'] # .refs provides access to all refs, i.e. heads ...
+ assert repo.refs['origin/master'] == repo.remotes.origin.refs.master # ... remotes ...
+ assert repo.refs['0.3.5'] == repo.tags['0.3.5'] # ... and tags
+ # ![8-test_init_repo_object]
+
+ # create a new head/branch
+ # [9-test_init_repo_object]
+ new_branch = cloned_repo.create_head('feature') # create a new branch ...
+ assert cloned_repo.active_branch != new_branch # which wasn't checked out yet ...
+ assert new_branch.commit == cloned_repo.active_branch.commit # and which points to the checked-out commit
+ # It's easy to let a branch point to the previous commit, without affecting anything else
+ # Each reference provides access to the git object it points to, usually commits
+ assert new_branch.set_commit('HEAD~1').commit == cloned_repo.active_branch.commit.parents[0]
+ # ![9-test_init_repo_object]
+
+ # create a new tag reference
+ # [10-test_init_repo_object]
+ past = cloned_repo.create_tag('past', ref=new_branch,
+ message="This is a tag-object pointing to %s" % new_branch.name)
+ assert past.commit == new_branch.commit # the tag points to the specified commit
+ assert past.tag.message.startswith("This is") # and its object carries the message provided
+
+ now = cloned_repo.create_tag('now') # This is a tag-reference. It may not carry meta-data
+ assert now.tag is None
+ # ![10-test_init_repo_object]
+
+ # Object handling
+ # [11-test_init_repo_object]
+ assert now.commit.message != past.commit.message
+ # You can read objects directly through binary streams, no working tree required
+ assert (now.commit.tree / 'VERSION').data_stream.read().decode('ascii').startswith('0')
+
+ # You can traverse trees as well to handle all contained files of a particular commit
+ file_count = 0
+ tree_count = 0
+ tree = past.commit.tree
+ for item in tree.traverse():
+ file_count += item.type == 'blob'
+ tree_count += item.type == 'tree'
+ assert file_count and tree_count # we have accumulated all directories and files
+ assert len(tree.blobs) + len(tree.trees) == len(tree) # a tree is iterable itself to traverse its children
+ # ![11-test_init_repo_object]
+
+ # remotes allow handling push, pull and fetch operations
+ # [12-test_init_repo_object]
+ from git import RemoteProgress
+
+ class MyProgressPrinter(RemoteProgress):
+ def update(self, op_code, cur_count, max_count=None, message=''):
+ print(op_code, cur_count, max_count, cur_count / (max_count or 100.0), message or "NO MESSAGE")
+ # end
+
+ assert len(cloned_repo.remotes) == 1 # we have been cloned, so there should be one remote
+ assert len(bare_repo.remotes) == 0 # this one was just initialized
+ origin = bare_repo.create_remote('origin', url=cloned_repo.working_tree_dir)
+ assert origin.exists()
+ for fetch_info in origin.fetch(progress=MyProgressPrinter()):
+ print("Updated %s to %s" % (fetch_info.ref, fetch_info.commit))
+ # create a local branch at the latest fetched master. We specify the name statically, but you have all
+ # information to do it programatically as well.
+ bare_master = bare_repo.create_head('master', origin.refs.master)
+ bare_repo.head.set_reference(bare_master)
+ assert not bare_repo.delete_remote(origin).exists()
+ # push and pull behave very similarly
+ # ![12-test_init_repo_object]
+
+ # index
+ # [13-test_init_repo_object]
+ assert new_branch.checkout() == cloned_repo.active_branch # checking out a branch adjusts the working tree
+ assert new_branch.commit == past.commit # Now the past is checked out
+
+ new_file_path = os.path.join(cloned_repo.working_tree_dir, 'my-new-file')
+ open(new_file_path, 'wb').close() # create new file in working tree
+ cloned_repo.index.add([new_file_path]) # add it to the index
+ # Commit the changes to deviate masters history
+ cloned_repo.index.commit("Added a new file in the past - for later merege")
+
+ # prepare a merge
+ master = cloned_repo.heads.master # right-hand side is ahead of us, in the future
+ merge_base = cloned_repo.merge_base(new_branch, master) # allwos for a three-way merge
+ cloned_repo.index.merge_tree(master, base=merge_base) # write the merge result into index
+ cloned_repo.index.commit("Merged past and now into future ;)",
+ parent_commits=(new_branch.commit, master.commit))
+
+ # now new_branch is ahead of master, which probably should be checked out and reset softly.
+ # note that all these operations didn't touch the working tree, as we managed it ourselves.
+ # This definitely requires you to know what you are doing :) !
+ assert os.path.basename(new_file_path) in new_branch.commit.tree # new file is now in tree
+ master.commit = new_branch.commit # let master point to most recent commit
+ cloned_repo.head.reference = master # we adjusted just the reference, not the working tree or index
+ # ![13-test_init_repo_object]
+
+ # submodules
+
+ # [14-test_init_repo_object]
+ # create a new submodule and check it out on the spot, setup to track master branch of `bare_repo`
+ # As our GitPython repository has submodules already that point to github, make sure we don't
+ # interact with them
+ for sm in cloned_repo.submodules:
+ assert not sm.remove().exists() # after removal, the sm doesn't exist anymore
+ sm = cloned_repo.create_submodule('mysubrepo', 'path/to/subrepo', url=bare_repo.git_dir, branch='master')
+
+ # .gitmodules was written and added to the index, which is now being committed
+ cloned_repo.index.commit("Added submodule")
+ assert sm.exists() and sm.module_exists() # this submodule is defintely available
+ sm.remove(module=True, configuration=False) # remove the working tree
+ assert sm.exists() and not sm.module_exists() # the submodule itself is still available
+
+ # update all submodules, non-recursively to save time, this method is very powerful, go have a look
+ cloned_repo.submodule_update(recursive=False)
+ assert sm.module_exists() # The submodules working tree was checked out by update
+ # ![14-test_init_repo_object]
+
@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 110f1fa5..d4a92ed4 100644
--- a/git/test/test_remote.py
+++ b/git/test/test_remote.py
@@ -451,11 +451,15 @@ class TestRemote(TestBase):
remote = Remote.create(bare_rw_repo, *arg_list)
assert remote.name == "test_new_one"
assert remote in bare_rw_repo.remotes
+ assert remote.exists()
# create same one again
self.failUnlessRaises(GitCommandError, Remote.create, bare_rw_repo, *arg_list)
Remote.remove(bare_rw_repo, new_name)
+ assert remote.exists() # We still have a cache that doesn't know we were deleted by name
+ remote._clear_cache()
+ assert not remote.exists() # Cache should be renewed now. This is an issue ...
for remote in bare_rw_repo.remotes:
if remote.name == new_name: