diff options
author | Sebastian Thiel <byronimo@gmail.com> | 2015-01-21 18:34:58 +0100 |
---|---|---|
committer | Sebastian Thiel <byronimo@gmail.com> | 2015-01-21 18:34:58 +0100 |
commit | e4d3809161fc54d6913c0c2c7f6a7b51eebe223f (patch) | |
tree | 1eebc9f43a1302c537da84e9a7219918da131f45 /git | |
parent | e48e52001d5abad7b28a4ecadde63c78c3946339 (diff) | |
download | gitpython-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.py | 9 | ||||
-rw-r--r-- | git/objects/base.py | 2 | ||||
-rw-r--r-- | git/objects/commit.py | 9 | ||||
-rw-r--r-- | git/objects/submodule/base.py | 2 | ||||
-rw-r--r-- | git/refs/reference.py | 5 | ||||
-rw-r--r-- | git/remote.py | 29 | ||||
-rw-r--r-- | git/repo/base.py | 6 | ||||
-rw-r--r-- | git/test/fixtures/diff_rename | 4 | ||||
-rw-r--r-- | git/test/test_diff.py | 6 | ||||
-rw-r--r-- | git/test/test_docs.py | 142 | ||||
-rw-r--r-- | git/test/test_remote.py | 4 |
11 files changed, 185 insertions, 33 deletions
@@ -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: |