From 141b78f42c7a3c1da1e5d605af3fc56aceb921ab Mon Sep 17 00:00:00 2001 From: avi Date: Fri, 17 Jul 2015 21:58:55 +0530 Subject: [PATCH 001/901] Added two extra paramaters for commit to take author date and commit date --- git/index/base.py | 5 +++-- git/objects/commit.py | 13 ++++++++++--- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index f86968007..78120da39 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -922,7 +922,7 @@ def move(self, items, skip_errors=False, **kwargs): return out - def commit(self, message, parent_commits=None, head=True, author=None, committer=None): + def commit(self, message, parent_commits=None, head=True, author=None, committer=None, author_date=None, commit_date=None): """Commit the current default index file, creating a commit object. For more information on the arguments, see tree.commit. @@ -932,7 +932,8 @@ def commit(self, message, parent_commits=None, head=True, author=None, committer run_commit_hook('pre-commit', self) tree = self.write_tree() rval = Commit.create_from_tree(self.repo, tree, message, parent_commits, - head, author=author, committer=committer) + head, author=author, committer=committer, + author_date=author_date, commit_date=commit_date) run_commit_hook('post-commit', self) return rval diff --git a/git/objects/commit.py b/git/objects/commit.py index d301e3014..376b451d9 100644 --- a/git/objects/commit.py +++ b/git/objects/commit.py @@ -266,7 +266,8 @@ def _iter_from_process_or_stream(cls, repo, proc_or_stream): finalize_process(proc_or_stream) @classmethod - def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None): + def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, author=None, committer=None, + author_date=None, commit_date=None): """Commit the given tree, creating a commit object. :param repo: Repo object the commit should be part of @@ -288,6 +289,8 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, configuration is used to obtain this value. :param committer: The name of the committer, optional. If unset, the repository configuration is used to obtain this value. + :param author_date: The timestamp for the author field + :param commit_date: The timestamp for the committer field :return: Commit object representing the new commit @@ -327,14 +330,18 @@ def create_from_tree(cls, repo, tree, message, parent_commits=None, head=False, offset = altzone author_date_str = env.get(cls.env_author_date, '') - if author_date_str: + if author_date: + author_time, author_offset = parse_date(author_date) + elif author_date_str: author_time, author_offset = parse_date(author_date_str) else: author_time, author_offset = unix_time, offset # END set author time committer_date_str = env.get(cls.env_committer_date, '') - if committer_date_str: + if commit_date: + committer_time, committer_offset = parse_date(commit_date) + elif committer_date_str: committer_time, committer_offset = parse_date(committer_date_str) else: committer_time, committer_offset = unix_time, offset From e3068025b64bee24efc1063aba5138708737c158 Mon Sep 17 00:00:00 2001 From: avi Date: Fri, 17 Jul 2015 22:18:46 +0530 Subject: [PATCH 002/901] added tests for commits with dates --- git/index/base.py | 3 ++- git/test/test_index.py | 11 +++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/git/index/base.py b/git/index/base.py index 78120da39..b955dae4e 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -922,7 +922,8 @@ def move(self, items, skip_errors=False, **kwargs): return out - def commit(self, message, parent_commits=None, head=True, author=None, committer=None, author_date=None, commit_date=None): + def commit(self, message, parent_commits=None, head=True, author=None, + committer=None, author_date=None, commit_date=None): """Commit the current default index file, creating a commit object. For more information on the arguments, see tree.commit. diff --git a/git/test/test_index.py b/git/test/test_index.py index 8c3775d2b..2fd53f656 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -470,6 +470,17 @@ def mixed_iterator(): assert cur_head.commit == commit_actor assert cur_head.log()[-1].actor == my_committer + # commit with author_date and commit_date + cur_commit = cur_head.commit + commit_message = u"commit with dates by Avinash Sajjanshetty" + + new_commit = index.commit(commit_message, author_date="2006-04-07T22:13:13", commit_date="2005-04-07T22:13:13") + assert cur_commit != new_commit + print(new_commit.authored_date, new_commit.committed_date) + assert new_commit.message == commit_message + assert new_commit.authored_date == 1144447993 + assert new_commit.committed_date == 1112911993 + # same index, no parents commit_message = "index without parents" commit_no_parents = index.commit(commit_message, parent_commits=list(), head=True) From 3438795d2af6d9639d1d6e9182ad916e73dd0c37 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Fri, 17 Jul 2015 22:09:05 +0200 Subject: [PATCH 003/901] typo in submodules api documentation --- git/objects/submodule/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 30201e090..610dddad7 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -431,7 +431,7 @@ def update(self, recursive=False, init=True, to_latest_revision=False, progress= This only works if we have a local tracking branch, which is the case if the remote repository had a master branch, or of the 'branch' option was specified for this submodule and the branch existed remotely - :param progress: UpdateProgress instance or None of no progress should be shown + :param progress: UpdateProgress instance or None if no progress should be shown :param dry_run: if True, the operation will only be simulated, but not performed. All performed operations are read-only :param force: From bdc38b83f4a6d39603dc845755df49065a19d029 Mon Sep 17 00:00:00 2001 From: "James E. Blair" Date: Fri, 17 Jul 2015 13:58:28 -0700 Subject: [PATCH 004/901] Always add '--' to git reset If a git repo has the misfortune to have a file with the name "HEAD" at the root level of the repo, git will return an error because it is unsure whether the file or ref is meant: File "/usr/local/lib/python2.7/dist-packages/git/refs/head.py", line 81, in reset self.repo.git.reset(mode, commit, add_arg, paths, **kwargs) File "/usr/local/lib/python2.7/dist-packages/git/cmd.py", line 440, in return lambda *args, **kwargs: self._call_process(name, *args, **kwargs) File "/usr/local/lib/python2.7/dist-packages/git/cmd.py", line 834, in _call_process return self.execute(make_call(), **_kwargs) File "/usr/local/lib/python2.7/dist-packages/git/cmd.py", line 627, in execute raise GitCommandError(command, status, stderr_value) GitCommandError: 'git reset --hard HEAD' returned with exit code 128 stderr: 'fatal: ambiguous argument 'HEAD': both revision and filename Use '--' to separate filenames from revisions' Implement its suggested fix by always passing '--' as an argument to "git reset". It is fine to pass it with no file specifiers afterwords. In that case, git knows that "HEAD" is always meant as the ref. --- git/refs/head.py | 7 +------ 1 file changed, 1 insertion(+), 6 deletions(-) diff --git a/git/refs/head.py b/git/refs/head.py index 18dac3497..06207e0ad 100644 --- a/git/refs/head.py +++ b/git/refs/head.py @@ -55,7 +55,6 @@ def reset(self, commit='HEAD', index=True, working_tree=False, :return: self""" mode = "--soft" - add_arg = None if index: mode = "--mixed" @@ -73,12 +72,8 @@ def reset(self, commit='HEAD', index=True, working_tree=False, # END working tree handling - if paths: - add_arg = "--" - # END nicely separate paths from rest - try: - self.repo.git.reset(mode, commit, add_arg, paths, **kwargs) + self.repo.git.reset(mode, commit, '--', paths, **kwargs) except GitCommandError as e: # git nowadays may use 1 as status to indicate there are still unstaged # modifications after the reset From 9c272abea2c837e4725c37f5c0467f83f3700cd5 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 20 Jul 2015 08:51:41 +0200 Subject: [PATCH 005/901] fix(encoding): in untracked_files() and index * untracked_files could, if there were spaces in the path returned, re-rencode the previously decoded unicode string thanks to a `decode("string_escape")` call. Now re-encode into utf-8 afterwards - added test to assure this works indeed * IndexFile.add() didn't handle unicode correctly and would write broken index files. The solution was to compute the path length after encoding it into utf-8 bytes, not before ... . Closes #320 --- git/index/base.py | 4 ++-- git/index/fun.py | 3 ++- git/repo/base.py | 2 +- git/test/test_repo.py | 28 +++++++++++++++------------- 4 files changed, 20 insertions(+), 17 deletions(-) diff --git a/git/index/base.py b/git/index/base.py index b955dae4e..4317d46a2 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -583,7 +583,7 @@ def _store_path(self, filepath, fprogress): stream = None if S_ISLNK(st.st_mode): # in PY3, readlink is string, but we need bytes. In PY2, it's just OS encoded bytes, we assume UTF-8 - stream = BytesIO(force_bytes(os.readlink(filepath), encoding='utf-8')) + stream = BytesIO(force_bytes(os.readlink(filepath), encoding=defenc)) else: stream = open(filepath, 'rb') # END handle stream @@ -610,7 +610,7 @@ def _entries_for_paths(self, paths, path_rewriter, fprogress, entries): blob = Blob(self.repo, Blob.NULL_BIN_SHA, stat_mode_to_index_mode(os.stat(abspath).st_mode), - to_native_path_linux(gitrelative_path)) + to_native_path_linux(gitrelative_path), encoding=defenc) # TODO: variable undefined entries.append(BaseIndexEntry.from_blob(blob)) # END for each path diff --git a/git/index/fun.py b/git/index/fun.py index c1188ccbc..9ae468611 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -124,12 +124,13 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1 write(entry[4]) # ctime write(entry[5]) # mtime path = entry[3] + path = path.encode(defenc) plen = len(path) & CE_NAMEMASK # path length assert plen == len(path), "Path %s too long to fit into index" % entry[3] flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values write(pack(">LLLLLL20sH", entry[6], entry[7], entry[0], entry[8], entry[9], entry[10], entry[1], flags)) - write(path.encode(defenc)) + write(path) real_size = ((tell() - beginoffset + 8) & ~7) write(b"\0" * ((beginoffset + real_size) - tell())) # END for each entry diff --git a/git/repo/base.py b/git/repo/base.py index 8b8f8db80..cea88f393 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -625,7 +625,7 @@ def _get_untracked_files(self, **kwargs): filename = line[len(prefix):].rstrip('\n') # Special characters are escaped if filename[0] == filename[-1] == '"': - filename = filename[1:-1].decode('string_escape') + filename = filename[1:-1].decode('string_escape').decode(defenc) untracked_files.append(filename) finalize_process(proc) return untracked_files diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 667ede74d..9c08e2e4c 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -1,3 +1,4 @@ +#-*-coding:utf-8-*- # test_repo.py # Copyright (C) 2008, 2009 Michael Trier (mtrier@gmail.com) and contributors # @@ -324,17 +325,21 @@ def test_blame_complex_revision(self, git): assert len(res) == 1 assert len(res[0][1]) == 83, "Unexpected amount of parsed blame lines" - def test_untracked_files(self): - base = self.rorepo.working_tree_dir - files = (join_path_native(base, "__test_myfile"), - join_path_native(base, "__test_other_file")) - num_recently_untracked = 0 - try: + @with_rw_repo('HEAD', bare=False) + def test_untracked_files(self, rwrepo): + for (run, repo_add) in enumerate((rwrepo.index.add, rwrepo.git.add)): + base = rwrepo.working_tree_dir + files = (join_path_native(base, u"%i_test _myfile" % run), + join_path_native(base, "%i_test_other_file" % run), + join_path_native(base, u"%i__çava verböten" % run), + join_path_native(base, u"%i_çava-----verböten" % run)) + + num_recently_untracked = 0 for fpath in files: fd = open(fpath, "wb") fd.close() # END for each filename - untracked_files = self.rorepo.untracked_files + untracked_files = rwrepo.untracked_files num_recently_untracked = len(untracked_files) # assure we have all names - they are relative to the git-dir @@ -342,13 +347,10 @@ def test_untracked_files(self): for utfile in untracked_files: num_test_untracked += join_path_native(base, utfile) in files assert len(files) == num_test_untracked - finally: - for fpath in files: - if os.path.isfile(fpath): - os.remove(fpath) - # END handle files - assert len(self.rorepo.untracked_files) == (num_recently_untracked - len(files)) + repo_add(untracked_files) + assert len(rwrepo.untracked_files) == (num_recently_untracked - len(files)) + # end for each run def test_config_reader(self): reader = self.rorepo.config_reader() # all config files From 3c8a33e2c9cae8deef1770a5fce85acb2e85b5c6 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 20 Jul 2015 09:19:38 +0200 Subject: [PATCH 006/901] fix(encoding): in `untracked_files()` I have no idea why PY3 requires such a mess of encoding/decoding statements, but let's just be happy it works. Also let's be sure I never ever write python code again ... EVER. --- git/repo/base.py | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/git/repo/base.py b/git/repo/base.py index cea88f393..5be63d860 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -54,7 +54,9 @@ ) from git.compat import ( text_type, - defenc + force_text, + defenc, + PY3 ) import os @@ -625,7 +627,12 @@ def _get_untracked_files(self, **kwargs): filename = line[len(prefix):].rstrip('\n') # Special characters are escaped if filename[0] == filename[-1] == '"': - filename = filename[1:-1].decode('string_escape').decode(defenc) + filename = filename[1:-1] + if PY3: + # WHATEVER ... it's a mess, but works for me + filename = filename.encode('ascii').decode('unicode_escape').encode('latin1').decode(defenc) + else: + filename = filename.decode('string_escape').decode(defenc) untracked_files.append(filename) finalize_process(proc) return untracked_files From c1d33021feb7324e0f2f91c947468bf282f036d2 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 20 Jul 2015 09:30:24 +0200 Subject: [PATCH 007/901] fix(index): remove invalid keyword argument It was a left-over of some prior hacking that was not removed by accident. --- git/index/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/index/base.py b/git/index/base.py index 4317d46a2..753fa4ae2 100644 --- a/git/index/base.py +++ b/git/index/base.py @@ -610,7 +610,7 @@ def _entries_for_paths(self, paths, path_rewriter, fprogress, entries): blob = Blob(self.repo, Blob.NULL_BIN_SHA, stat_mode_to_index_mode(os.stat(abspath).st_mode), - to_native_path_linux(gitrelative_path), encoding=defenc) + to_native_path_linux(gitrelative_path)) # TODO: variable undefined entries.append(BaseIndexEntry.from_blob(blob)) # END for each path From f360ecd7b2de173106c08238ec60db38ec03ee9b Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 20 Jul 2015 09:57:53 +0200 Subject: [PATCH 008/901] fix(flake8): remove unused import I knew that flake would eventually get me, especially when least suspected. This time it's even useful, as it is a non-whitespace related issue. --- git/repo/base.py | 1 - 1 file changed, 1 deletion(-) diff --git a/git/repo/base.py b/git/repo/base.py index 5be63d860..b04112353 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -54,7 +54,6 @@ ) from git.compat import ( text_type, - force_text, defenc, PY3 ) From 65c07d64a7b1dc85c41083c60a8082b3705154c3 Mon Sep 17 00:00:00 2001 From: Sandy Carter Date: Tue, 21 Jul 2015 11:02:24 -0400 Subject: [PATCH 009/901] Implement is_ancestor Wrap `git merge-base --is-ancestor` into its own function because it acts as a boolean check unlike base `git merge-base call` --- git/repo/base.py | 15 +++++++++++++++ git/test/test_repo.py | 14 ++++++++++++++ 2 files changed, 29 insertions(+) diff --git a/git/repo/base.py b/git/repo/base.py index b04112353..16fb58e59 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -504,6 +504,21 @@ def merge_base(self, *rev, **kwargs): return res + def is_ancestor(self, ancestor_rev, rev): + """Check if a commit is an ancestor of another + + :param ancestor_rev: Rev which should be an ancestor + :param rev: Rev to test against ancestor_rev + :return: ``True``, ancestor_rev is an accestor to rev. + """ + try: + self.git.merge_base(ancestor_rev, rev, is_ancestor=True) + except GitCommandError as err: + if err.status == 1: + return False + raise + return True + def _get_daemon_export(self): filename = join(self.git_dir, self.DAEMON_EXPORT_FILE) return os.path.exists(filename) diff --git a/git/test/test_repo.py b/git/test/test_repo.py index 9c08e2e4c..bc9f3e92c 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -42,6 +42,7 @@ import sys import tempfile import shutil +import itertools from io import BytesIO @@ -765,3 +766,16 @@ def test_merge_base(self): # Test for no merge base - can't do as we have self.failUnlessRaises(GitCommandError, repo.merge_base, c1, 'ffffff') + + def test_is_ancestor(self): + repo = self.rorepo + c1 = 'f6aa8d1' + c2 = '763ef75' + self.assertTrue(repo.is_ancestor(c1, c1)) + self.assertTrue(repo.is_ancestor("master", "master")) + self.assertTrue(repo.is_ancestor(c1, c2)) + self.assertTrue(repo.is_ancestor(c1, "master")) + self.assertFalse(repo.is_ancestor(c2, c1)) + self.assertFalse(repo.is_ancestor("master", c1)) + for i, j in itertools.permutations([c1, 'ffffff', ''], r=2): + self.assertRaises(GitCommandError, repo.is_ancestor, i, j) From 6fdb6a5eac7433098cfbb33d3e18d6dbba8fa3d3 Mon Sep 17 00:00:00 2001 From: Julien Pivotto Date: Wed, 22 Jul 2015 09:28:42 +0200 Subject: [PATCH 010/901] gic {init,clone} --separate-git-dir is supported only since 1.7.5 Without this commit the update() function of a submodule does not work in CentOS 6. --- git/objects/submodule/base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/objects/submodule/base.py b/git/objects/submodule/base.py index 610dddad7..eea091f8c 100644 --- a/git/objects/submodule/base.py +++ b/git/objects/submodule/base.py @@ -137,7 +137,7 @@ def _get_intermediate_items(self, item): @classmethod def _need_gitfile_submodules(cls, git): - return git.version_info[:3] >= (1, 7, 0) + return git.version_info[:3] >= (1, 7, 5) def __eq__(self, other): """Compare with another submodule""" From 58c5b993d218c0ebc3f610c2e55a14b194862e1c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?David=20Garc=C3=ADa=20Garz=C3=B3n?= Date: Sun, 26 Jul 2015 15:42:00 +0200 Subject: [PATCH 011/901] Ensure file resources are released --- git/refs/symbolic.py | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/git/refs/symbolic.py b/git/refs/symbolic.py index d884250f7..f3799ed4e 100644 --- a/git/refs/symbolic.py +++ b/git/refs/symbolic.py @@ -88,25 +88,25 @@ def _iter_packed_refs(cls, repo): """Returns an iterator yielding pairs of sha1/path pairs (as bytes) for the corresponding refs. :note: The packed refs file will be kept open as long as we iterate""" try: - fp = open(cls._get_packed_refs_path(repo), 'rt') - for line in fp: - line = line.strip() - if not line: - continue - if line.startswith('#'): - if line.startswith('# pack-refs with:') and not line.endswith('peeled'): - raise TypeError("PackingType of packed-Refs not understood: %r" % line) - # END abort if we do not understand the packing scheme - continue - # END parse comment + with open(cls._get_packed_refs_path(repo), 'rt') as fp: + for line in fp: + line = line.strip() + if not line: + continue + if line.startswith('#'): + if line.startswith('# pack-refs with:') and not line.endswith('peeled'): + raise TypeError("PackingType of packed-Refs not understood: %r" % line) + # END abort if we do not understand the packing scheme + continue + # END parse comment - # skip dereferenced tag object entries - previous line was actual - # tag reference for it - if line[0] == '^': - continue + # skip dereferenced tag object entries - previous line was actual + # tag reference for it + if line[0] == '^': + continue - yield tuple(line.split(' ', 1)) - # END for each line + yield tuple(line.split(' ', 1)) + # END for each line except (OSError, IOError): raise StopIteration # END no packed-refs file handling From eb6beb75aaa269a1e7751d389c0826646878e5fc Mon Sep 17 00:00:00 2001 From: Ram Rachum Date: Tue, 28 Jul 2015 00:18:57 +0300 Subject: [PATCH 012/901] Fix bug in tutorial --- doc/source/tutorial.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/doc/source/tutorial.rst b/doc/source/tutorial.rst index 7cc296d83..2533216c9 100644 --- a/doc/source/tutorial.rst +++ b/doc/source/tutorial.rst @@ -393,7 +393,7 @@ 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.commit)`` * A diff between the index and the working tree From c58887a2d8554d171a7c76b03bfa919c72e918e1 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Tue, 28 Jul 2015 17:51:50 +0200 Subject: [PATCH 013/901] don't 'log' to stderr in `RemoteProgress` There is simply no excuse to doing that. Closes #330 --- git/util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/git/util.py b/git/util.py index f0a08e799..809bcc048 100644 --- a/git/util.py +++ b/git/util.py @@ -239,7 +239,6 @@ def _parse_progress_line(self, line): # to make sure we get informed in case the process spits out new # commands at some point. self.line_dropped(sline) - sys.stderr.write("Operation name %r unknown - skipping line '%s'" % (op_name, sline)) # Note: Don't add this line to the failed lines, as we have to silently # drop it return failed_lines From 8324c4b38cf37af416833d36696577d8d35dce7f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 29 Jul 2015 15:14:41 +0200 Subject: [PATCH 014/901] fix(diff): mode-assertions now deal with 0 If the file was not present, the mode seen in a diff can be legally '0', which previously caused an assertion to fail for no good reason. Now the assertion tests for None instead. Closes #323 --- git/diff.py | 4 ++-- git/test/test_diff.py | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/git/diff.py b/git/diff.py index dc53f3f71..9059091e6 100644 --- a/git/diff.py +++ b/git/diff.py @@ -223,12 +223,12 @@ def __init__(self, repo, a_path, b_path, a_blob_id, b_blob_id, a_mode, if a_blob_id is None: self.a_blob = None else: - assert self.a_mode + assert self.a_mode is not None self.a_blob = Blob(repo, hex_to_bin(a_blob_id), mode=self.a_mode, path=a_path) if b_blob_id is None: self.b_blob = None else: - assert self.b_mode + assert self.b_mode is not None self.b_blob = Blob(repo, hex_to_bin(b_blob_id), mode=self.b_mode, path=b_path) self.new_file = new_file diff --git a/git/test/test_diff.py b/git/test/test_diff.py index f2ce14471..53bb65dbd 100644 --- a/git/test/test_diff.py +++ b/git/test/test_diff.py @@ -4,6 +4,7 @@ # # This module is part of GitPython and is released under # the BSD License: http://www.opensource.org/licenses/bsd-license.php +import os from git.test.lib import ( TestBase, @@ -14,7 +15,11 @@ ) +from gitdb.test.lib import with_rw_directory + from git import ( + Repo, + GitCommandError, Diff, DiffIndex ) @@ -37,6 +42,33 @@ def _assert_diff_format(self, diffs): # END for each diff return diffs + @with_rw_directory + def test_diff_with_staged_file(self, rw_dir): + # SETUP INDEX WITH MULTIPLE STAGES + r = Repo.init(rw_dir) + fp = os.path.join(rw_dir, 'hello.txt') + with open(fp, 'w') as fs: + fs.write("hello world") + r.git.add(fp) + r.git.commit(message="init") + + with open(fp, 'w') as fs: + fs.write("Hola Mundo") + r.git.commit(all=True, message="change on master") + + r.git.checkout('HEAD~1', b='topic') + with open(fp, 'w') as fs: + fs.write("Hallo Welt") + r.git.commit(all=True, message="change on topic branch") + + # there must be a merge-conflict + self.failUnlessRaises(GitCommandError, r.git.cherry_pick, 'master') + + # Now do the actual testing - this should just work + assert len(r.index.diff(None)) == 2 + + assert len(r.index.diff(None, create_patch=True)) == 0, "This should work, but doesn't right now ... it's OK" + def test_list_from_string_new_mode(self): output = StringProcessAdapter(fixture('diff_new_mode')) diffs = Diff._index_from_patch_format(self.rorepo, output.stdout) From 7ab12b403207bb46199f46d5aaa72d3e82a3080d Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Wed, 29 Jul 2015 18:29:03 +0200 Subject: [PATCH 015/901] fix(index):allow adding non-unicode paths to index This issue only surfaced in python 2, in case paths containing unicode characters were not actual unicode objects. In python 3, this was never the issue. Closes #331 --- git/index/fun.py | 5 +++-- git/test/test_index.py | 13 +++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/git/index/fun.py b/git/index/fun.py index 9ae468611..c1026fd62 100644 --- a/git/index/fun.py +++ b/git/index/fun.py @@ -41,7 +41,8 @@ from gitdb.typ import str_tree_type from git.compat import ( defenc, - force_text + force_text, + force_bytes ) S_IFGITLINK = S_IFLNK | S_IFDIR # a submodule @@ -124,7 +125,7 @@ def write_cache(entries, stream, extension_data=None, ShaStreamCls=IndexFileSHA1 write(entry[4]) # ctime write(entry[5]) # mtime path = entry[3] - path = path.encode(defenc) + path = force_bytes(path, encoding=defenc) plen = len(path) & CE_NAMEMASK # path length assert plen == len(path), "Path %s too long to fit into index" % entry[3] flags = plen | (entry[2] & CE_NAMEMASK_INV) # clear possible previous values diff --git a/git/test/test_index.py b/git/test/test_index.py index 2fd53f656..397885752 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -18,6 +18,7 @@ ) from git import ( IndexFile, + Repo, BlobFilter, UnmergedEntriesError, Tree, @@ -45,6 +46,7 @@ IndexEntry ) from git.index.fun import hook_path +from gitdb.test.lib import with_rw_directory class TestIndex(TestBase): @@ -780,3 +782,14 @@ def test_index_bare_add(self, rw_bare_repo): except InvalidGitRepositoryError: asserted = True assert asserted, "Adding using a filename is not correctly asserted." + + @with_rw_directory + def test_add_utf8P_path(self, rw_dir): + # NOTE: fp is not a Unicode object in python 2 (which is the source of the problem) + fp = os.path.join(rw_dir, 'ø.txt') + with open(fp, 'w') as fs: + fs.write('content of ø') + + r = Repo.init(rw_dir) + r.index.add([fp]) + r.index.commit('Added orig and prestable') From b2e4134963c971e3259137b84237d6c47964b018 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 8 Aug 2015 22:25:09 +0200 Subject: [PATCH 016/901] docs(README): removed coveralls link Coveralls was disabled a while ago because it would prevent merging PRs from the phone. --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index f87cb0efe..75fcdcb2c 100644 --- a/README.md +++ b/README.md @@ -78,7 +78,6 @@ New BSD License. See the LICENSE file. [![Build Status](https://travis-ci.org/gitpython-developers/GitPython.svg)](https://travis-ci.org/gitpython-developers/GitPython) [![Code Climate](https://codeclimate.com/github/gitpython-developers/GitPython/badges/gpa.svg)](https://codeclimate.com/github/gitpython-developers/GitPython) -[![Coverage Status](https://coveralls.io/repos/gitpython-developers/GitPython/badge.png?branch=master)](https://coveralls.io/r/gitpython-developers/GitPython?branch=master) [![Documentation Status](https://readthedocs.org/projects/gitpython/badge/?version=stable)](https://readthedocs.org/projects/gitpython/?badge=stable) [![Issue Stats](http://www.issuestats.com/github/gitpython-developers/GitPython/badge/pr)](http://www.issuestats.com/github/gitpython-developers/GitPython) [![Issue Stats](http://www.issuestats.com/github/gitpython-developers/GitPython/badge/issue)](http://www.issuestats.com/github/gitpython-developers/GitPython) From a0acb2229e1ebb59ab343e266fc5c1cc392a974e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 8 Aug 2015 23:02:38 +0200 Subject: [PATCH 017/901] tests(submodule): add new submodule commits It's somewhat more complex to add new commits in submodules to the parent repository. The new test shows how to do that in a 'GitPythonic' way. Related to #335 --- git/test/test_submodule.py | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index cbf38c182..3d78d0672 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -661,7 +661,7 @@ def test_add_empty_repo(self, rwdir): # end for each checkout mode @with_rw_directory - def test_git_submodules(self, rwdir): + def test_git_submodules_and_add_sm_with_new_commit(self, rwdir): parent = git.Repo.init(os.path.join(rwdir, 'parent')) parent.git.submodule('add', self._small_repo_url(), 'module') parent.index.commit("added submodule") @@ -686,6 +686,37 @@ def test_git_submodules(self, rwdir): sm.move(sm.path + '_moved') sm2.move(sm2.path + '_moved') + parent.index.commit("moved submodules") + + smm = sm.module() + fp = os.path.join(smm.working_tree_dir, 'empty-file') + with open(fp, 'w'): + pass + smm.git.add(fp) + smm.git.commit(m="new file added") + + # submodules are retrieved from the current commit's tree, therefore we can't really get a new submodule + # object pointing to the new submodule commit + sm_too = parent.submodules[0] + assert parent.head.commit.tree[sm.path].binsha == sm.binsha + assert sm_too.binsha == sm.binsha, "cached submodule should point to the same commit as updated one" + + added_bies = parent.index.add([sm]) # addded base-index-entries + assert len(added_bies) == 1 + parent.index.commit("add same submodule entry") + commit_sm = parent.head.commit.tree[sm.path] + assert commit_sm.binsha == added_bies[0].binsha + assert commit_sm.binsha == sm.binsha + + sm_too.binsha = sm_too.module().head.commit.binsha + added_bies = parent.index.add([sm_too]) + assert len(added_bies) == 1 + parent.index.commit("add new submodule entry") + commit_sm = parent.head.commit.tree[sm.path] + assert commit_sm.binsha == added_bies[0].binsha + assert commit_sm.binsha == sm_too.binsha + assert sm_too.binsha != sm.binsha + @with_rw_directory def test_git_submodule_compatibility(self, rwdir): parent = git.Repo.init(os.path.join(rwdir, 'parent')) From 039e265819cc6e5241907f1be30d2510bfa5ca6c Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 8 Aug 2015 23:35:12 +0200 Subject: [PATCH 018/901] fix(tests): remove dependency on sort order Now we select the submodule by name, not by index. The latter is not deterministic. Closes #335 --- git/test/test_submodule.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/test/test_submodule.py b/git/test/test_submodule.py index 3d78d0672..17ce605a4 100644 --- a/git/test/test_submodule.py +++ b/git/test/test_submodule.py @@ -697,7 +697,7 @@ def test_git_submodules_and_add_sm_with_new_commit(self, rwdir): # submodules are retrieved from the current commit's tree, therefore we can't really get a new submodule # object pointing to the new submodule commit - sm_too = parent.submodules[0] + sm_too = parent.submodules['module_moved'] assert parent.head.commit.tree[sm.path].binsha == sm.binsha assert sm_too.binsha == sm.binsha, "cached submodule should point to the same commit as updated one" From a8f7e3772f68c8e6350b9ff5ac981ba3223f2d43 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 17 Aug 2015 22:32:15 +0200 Subject: [PATCH 019/901] fix(commit): serialization timezone handling Previously timezones which were not divisable by 3600s would be parsed correctly, but would serialize into a full hour, rounded up. Now floating point computation is used which fixes the issue. Related to #336 --- doc/source/changes.rst | 7 +++++++ git/objects/util.py | 2 +- git/test/performance/test_commit.py | 2 +- 3 files changed, 9 insertions(+), 2 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index e6d7b09be..970ba1958 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -2,6 +2,13 @@ Changelog ========= +1.0.2 - Fixes +============= + +* CRITICAL: fixed incorrect `Commit` object serialization when authored or commit date had timezones which were not + divisable by 3600 seconds. This would happen if the timezone was something like `+0530` for instance. +* A list of all additional fixes can be found `on github `_ + 1.0.1 - Fixes ============= diff --git a/git/objects/util.py b/git/objects/util.py index 567b1d5b2..8fd92a0a9 100644 --- a/git/objects/util.py +++ b/git/objects/util.py @@ -73,7 +73,7 @@ def utctz_to_altz(utctz): def altz_to_utctz_str(altz): """As above, but inverses the operation, returning a string that can be used in commit objects""" - utci = -1 * int((altz / 3600) * 100) + utci = -1 * int((float(altz) / 3600) * 100) utcs = str(abs(utci)) utcs = "0" * (4 - len(utcs)) + utcs prefix = (utci < 0 and '-') or '+' diff --git a/git/test/performance/test_commit.py b/git/test/performance/test_commit.py index 7d3e87c4c..b59c747ee 100644 --- a/git/test/performance/test_commit.py +++ b/git/test/performance/test_commit.py @@ -76,7 +76,7 @@ def test_commit_iteration(self): % (nc, elapsed_time, nc / elapsed_time), file=sys.stderr) def test_commit_serialization(self): - assert_commit_serialization(self.gitrwrepo, self.gitrwrepo.head, True) + assert_commit_serialization(self.gitrwrepo, '58c78e6', True) rwrepo = self.gitrwrepo make_object = rwrepo.odb.store From c8b837923d506e265ff8bb79af61c0d86e7d5b2e Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Mon, 17 Aug 2015 22:39:13 +0200 Subject: [PATCH 020/901] fix(test_index): fix encoding I really never want to touch python again, and never deal with py2/3 unicode handling anymore. By now, it seems pretty much anything is better. Is python to be blamed for it entirely ? Probably not, but there are better alternatives. Nim ? Rust ? Ruby ? Totally --- git/test/test_index.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/git/test/test_index.py b/git/test/test_index.py index 397885752..ffc4bffe1 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -787,8 +787,8 @@ def test_index_bare_add(self, rw_bare_repo): def test_add_utf8P_path(self, rw_dir): # NOTE: fp is not a Unicode object in python 2 (which is the source of the problem) fp = os.path.join(rw_dir, 'ø.txt') - with open(fp, 'w') as fs: - fs.write('content of ø') + with open(fp, 'wb') as fs: + fs.write(u'content of ø'.encode('utf-8')) r = Repo.init(rw_dir) r.index.add([fp]) From 332521ac1d94f743b06273e6a8daf91ce93aed7d Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 20 Aug 2015 00:09:26 +0200 Subject: [PATCH 021/901] fix(cmd): make short options with arguments become two separate arguments for the executable. --- git/cmd.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/cmd.py b/git/cmd.py index 31865d090..3cdc68ab7 100644 --- a/git/cmd.py +++ b/git/cmd.py @@ -700,7 +700,7 @@ def custom_environment(self, **kwargs): finally: self.update_environment(**old_env) - def transform_kwargs(self, split_single_char_options=False, **kwargs): + def transform_kwargs(self, split_single_char_options=True, **kwargs): """Transforms Python style kwargs into git command line options.""" args = list() for k, v in kwargs.items(): From ec15e53439d228ec64cb260e02aeae5cc05c5b2b Mon Sep 17 00:00:00 2001 From: Marcos Dione Date: Thu, 20 Aug 2015 00:18:48 +0200 Subject: [PATCH 022/901] fix(test): update to changes. --- git/test/test_git.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/git/test/test_git.py b/git/test/test_git.py index f386150f5..3e3e21e48 100644 --- a/git/test/test_git.py +++ b/git/test/test_git.py @@ -65,7 +65,7 @@ def test_it_raises_errors(self): def test_it_transforms_kwargs_into_git_command_arguments(self): assert_equal(["-s"], self.git.transform_kwargs(**{'s': True})) - assert_equal(["-s5"], self.git.transform_kwargs(**{'s': 5})) + assert_equal(["-s", "5"], self.git.transform_kwargs(**{'s': 5})) assert_equal(["--max-count"], self.git.transform_kwargs(**{'max_count': True})) assert_equal(["--max-count=5"], self.git.transform_kwargs(**{'max_count': 5})) From 6eb3af27464ffba83e3478b0a0c8b1f9ff190889 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 22 Aug 2015 16:31:31 +0200 Subject: [PATCH 023/901] fix(repo): use GitCmdObjectDB by default This should fix resource leaking issues once and for all. Related #304 --- doc/source/changes.rst | 3 +++ git/repo/base.py | 7 ++----- git/test/test_repo.py | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/doc/source/changes.rst b/doc/source/changes.rst index 970ba1958..acf681eea 100644 --- a/doc/source/changes.rst +++ b/doc/source/changes.rst @@ -5,6 +5,9 @@ Changelog 1.0.2 - Fixes ============= +* IMPORTANT: Changed default object database of `Repo` objects to `GitComdObjectDB`. The pure-python implementation + used previously usually fails to release its resources (i.e. file handles), which can lead to problems when working + with large repositories. * CRITICAL: fixed incorrect `Commit` object serialization when authored or commit date had timezones which were not divisable by 3600 seconds. This would happen if the timezone was something like `+0530` for instance. * A list of all additional fixes can be found `on github `_ diff --git a/git/repo/base.py b/git/repo/base.py index 16fb58e59..d5bc24d87 100644 --- a/git/repo/base.py +++ b/git/repo/base.py @@ -35,10 +35,7 @@ add_progress ) -from git.db import ( - GitCmdObjectDB, - GitDB -) +from git.db import GitCmdObjectDB from gitdb.util import ( join, @@ -62,7 +59,7 @@ import sys import re -DefaultDBType = GitDB +DefaultDBType = GitCmdObjectDB if sys.version_info[:2] < (2, 5): # python 2.4 compatiblity DefaultDBType = GitCmdObjectDB # END handle python 2.4 diff --git a/git/test/test_repo.py b/git/test/test_repo.py index bc9f3e92c..c95592ea9 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -657,7 +657,7 @@ def test_rev_parse(self): assert rev_parse('@{1}') != head.commit def test_repo_odbtype(self): - target_type = GitDB + target_type = GitCmdObjectDB if sys.version_info[:2] < (2, 5): target_type = GitCmdObjectDB assert isinstance(self.rorepo.odb, target_type) From e8590424997ab1d578c777fe44bf7e4173036f93 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 29 Aug 2015 16:19:52 +0200 Subject: [PATCH 024/901] fix(repo): fail loudly if worktrees are used As GitPython is in maintenance mode, there will be no new features. However, I believe it's good idea to explicitly state we do not support certain things if this is the case. Therefore, when worktrees are encountered, we will throw an specific exception to indicate that. The current implementation is hacky to speed up development, and increases the risk of failing due to false-positive worktree directories. Related to #344 --- git/exc.py | 4 ++++ git/repo/fun.py | 25 ++++++++++++++++--------- git/test/test_repo.py | 20 +++++++++++++++++++- 3 files changed, 39 insertions(+), 10 deletions(-) diff --git a/git/exc.py b/git/exc.py index f5b52374b..34382ecd5 100644 --- a/git/exc.py +++ b/git/exc.py @@ -14,6 +14,10 @@ class InvalidGitRepositoryError(Exception): """ Thrown if the given repository appears to have an invalid format. """ +class WorkTreeRepositoryUnsupported(InvalidGitRepositoryError): + """ Thrown to indicate we can't handle work tree repositories """ + + class NoSuchPathError(OSError): """ Thrown if a path could not be access by the system. """ diff --git a/git/repo/fun.py b/git/repo/fun.py index 049800b93..6b06663a0 100644 --- a/git/repo/fun.py +++ b/git/repo/fun.py @@ -4,7 +4,7 @@ from gitdb.exc import ( BadObject, - BadName + BadName, ) from git.refs import SymbolicReference from git.objects import Object @@ -16,6 +16,7 @@ hex_to_bin, bin_to_hex ) +from git.exc import WorkTreeRepositoryUnsupported from git.compat import xrange @@ -31,14 +32,20 @@ def touch(filename): def is_git_dir(d): """ This is taken from the git setup.c:is_git_directory - function.""" - if isdir(d) and \ - isdir(join(d, 'objects')) and \ - isdir(join(d, 'refs')): - headref = join(d, 'HEAD') - return isfile(headref) or \ - (os.path.islink(headref) and - os.readlink(headref).startswith('refs')) + function. + + @throws WorkTreeRepositoryUnsupported if it sees a worktree directory. It's quite hacky to do that here, + but at least clearly indicates that we don't support it. + There is the unlikely danger to throw if we see directories which just look like a worktree dir, + but are none.""" + if isdir(d): + if isdir(join(d, 'objects')) and isdir(join(d, 'refs')): + headref = join(d, 'HEAD') + return isfile(headref) or \ + (os.path.islink(headref) and + os.readlink(headref).startswith('refs')) + elif isfile(join(d, 'gitdir')) and isfile(join(d, 'commondir')) and isfile(join(d, 'gitfile')): + raise WorkTreeRepositoryUnsupported(d) return False diff --git a/git/test/test_repo.py b/git/test/test_repo.py index c95592ea9..2e44f0aad 100644 --- a/git/test/test_repo.py +++ b/git/test/test_repo.py @@ -33,7 +33,10 @@ ) from git.repo.fun import touch from git.util import join_path_native -from git.exc import BadObject +from git.exc import ( + BadObject, + WorkTreeRepositoryUnsupported +) from gitdb.util import bin_to_hex from git.compat import string_types from gitdb.test.lib import with_rw_directory @@ -45,6 +48,8 @@ import itertools from io import BytesIO +from nose import SkipTest + class TestRepo(TestBase): @@ -779,3 +784,16 @@ def test_is_ancestor(self): self.assertFalse(repo.is_ancestor("master", c1)) for i, j in itertools.permutations([c1, 'ffffff', ''], r=2): self.assertRaises(GitCommandError, repo.is_ancestor, i, j) + + @with_rw_directory + def test_work_tree_unsupported(self, rw_dir): + git = Git(rw_dir) + if git.version_info[:3] < (2, 5, 1): + raise SkipTest("worktree feature unsupported") + + rw_master = self.rorepo.clone(join_path_native(rw_dir, 'master_repo')) + rw_master.git.checkout('HEAD~10') + worktree_path = join_path_native(rw_dir, 'worktree_repo') + rw_master.git.worktree('add', worktree_path, 'master') + + self.failUnlessRaises(WorkTreeRepositoryUnsupported, Repo, worktree_path) From 7f8d9ca08352a28cba3b01e4340a24edc33e13e8 Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sat, 29 Aug 2015 16:25:40 +0200 Subject: [PATCH 025/901] fix(compat): make test work with git >= 2.5 --- git/test/test_index.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/git/test/test_index.py b/git/test/test_index.py index ffc4bffe1..a928fe5e7 100644 --- a/git/test/test_index.py +++ b/git/test/test_index.py @@ -690,6 +690,9 @@ def make_paths(): index.add(files, write=True) if os.name != 'nt': hp = hook_path('pre-commit', index.repo.git_dir) + hpd = os.path.dirname(hp) + if not os.path.isdir(hpd): + os.mkdir(hpd) with open(hp, "wt") as fp: fp.write("#!/usr/bin/env sh\necho stdout; echo stderr 1>&2; exit 1") # end From 074842accb51b2a0c2c1193018d9f374ac5e948f Mon Sep 17 00:00:00 2001 From: Sebastian Thiel Date: Sun, 6 Sep 2015 15:11:54 +0200 Subject: [PATCH 026/901] fix(config): ignore empty values in config file Similar to git, we now ignore options which have no value. Previously it would not handle it consistently, and throw a parsing error the first time the cache was built. Afterwards, it was fully usable though. Now we specifically check for the case of no-value options instead. Closes #349 --- git/config.py | 20 +++++++++++++------ git/test/fixtures/git_config_with_empty_value | 4 ++++ git/test/test_config.py | 7 +++++++ 3 files changed, 25 insertions(+), 6 deletions(-) create mode 100644 git/test/fixtures/git_config_with_empty_value diff --git a/git/config.py b/git/config.py index b7ddf0d22..ea5e17bea 100644 --- a/git/config.py +++ b/git/config.py @@ -156,15 +156,21 @@ class GitConfigParser(with_metaclass(MetaParserBuilder, cp.RawConfigParser, obje #} END configuration + optvalueonly_source = r'\s*(?P