| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -35,5 +35,6 @@ Contributors are: | |||
| 35 | 35 | -César Izurieta <cesar _at_ caih.org> | |
| 36 | 36 | -Arthur Milchior <arthur _at_ milchior.fr> | |
| 37 | 37 | -Anil Khatri <anil.soccer.khatri _at_ gmail.com> | |
| 38 | + -JJ Graham <thetwoj _at_ gmail.com> | ||
| 38 | 39 | ||
| 39 | 40 | Portions derived from other open source works and are clearly marked. | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -110,12 +110,13 @@ Please have a look at the [contributions file][contributing]. | |||
| 110 | 110 | ||
| 111 | 111 | ### How to make a new release | |
| 112 | 112 | ||
| 113 | - * Update/verify the version in the `VERSION` file | ||
| 114 | - * Update/verify that the changelog has been updated | ||
| 113 | + * Update/verify the **version** in the `VERSION` file | ||
| 114 | + * Update/verify that the `doc/source/changes.rst` changelog file was updated | ||
| 115 | 115 | * Commit everything | |
| 116 | 116 | * Run `git tag -s <version>` to tag the version in Git | |
| 117 | 117 | * Run `make release` | |
| 118 | - * Finally, set the upcoming version in the `VERSION` file, usually be | ||
| 118 | + * Close the milestone mentioned in the _changelog_ and create a new one. _Do not reuse milestones by renaming them_. | ||
| 119 | + * set the upcoming version in the `VERSION` file, usually be | ||
| 119 | 120 | incrementing the patch level, and possibly by appending `-dev`. Probably you | |
| 120 | 121 | want to `git push` once more. | |
| 121 | 122 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -1 +1 @@ | |||
| 1 | - 3.0.3 | ||
| 1 | + 3.0.4 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -2,6 +2,12 @@ | |||
| 2 | 2 | Changelog | |
| 3 | 3 | ========= | |
| 4 | 4 | ||
| 5 | + 3.0.4 - Bugfixes | ||
| 6 | + ============================================= | ||
| 7 | + | ||
| 8 | + see the following for details: | ||
| 9 | + https://github.com/gitpython-developers/gitpython/milestone/31?closed=1 | ||
| 10 | + | ||
| 5 | 11 | 3.0.3 - Bugfixes | |
| 6 | 12 | ============================================= | |
| 7 | 13 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -67,7 +67,7 @@ | |||
| 67 | 67 | ||
| 68 | 68 | def handle_process_output(process, stdout_handler, stderr_handler, | |
| 69 | 69 | finalizer=None, decode_streams=True): | |
| 70 | - """Registers for notifications to lean that process output is ready to read, and dispatches lines to | ||
| 70 | + """Registers for notifications to learn that process output is ready to read, and dispatches lines to | ||
| 71 | 71 | the respective line handlers. | |
| 72 | 72 | This function returns once the finalizer returns | |
| 73 | 73 | ||
@@ -330,6 +330,9 @@ def polish_url(cls, url, is_cygwin=None): | |||
| 330 | 330 | but git stops liking them as it will escape the backslashes. | |
| 331 | 331 | Hence we undo the escaping just to be sure. | |
| 332 | 332 | """ | |
| 333 | + url = os.path.expandvars(url) | ||
| 334 | + if url.startswith('~'): | ||
| 335 | + url = os.path.expanduser(url) | ||
| 333 | 336 | url = url.replace("\\\\", "\\").replace("\\", "/") | |
| 334 | 337 | ||
| 335 | 338 | return url | |
@@ -362,8 +365,11 @@ def __del__(self): | |||
| 362 | 365 | proc.stderr.close() | |
| 363 | 366 | ||
| 364 | 367 | # did the process finish already so we have a return code ? | |
| 365 | - if proc.poll() is not None: | ||
| 366 | - return | ||
| 368 | + try: | ||
| 369 | + if proc.poll() is not None: | ||
| 370 | + return | ||
| 371 | + except OSError as ex: | ||
| 372 | + log.info("Ignored error after process had died: %r", ex) | ||
| 367 | 373 | ||
| 368 | 374 | # can be that nothing really exists anymore ... | |
| 369 | 375 | if os is None or getattr(os, 'kill', None) is None: | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -167,7 +167,7 @@ class DiffIndex(list): | |||
| 167 | 167 | # R = Renamed | |
| 168 | 168 | # M = Modified | |
| 169 | 169 | # T = Changed in the type | |
| 170 | - change_type = ("A", "D", "R", "M", "T") | ||
| 170 | + change_type = ("A", "C", "D", "R", "M", "T") | ||
| 171 | 171 | ||
| 172 | 172 | def iter_change_type(self, change_type): | |
| 173 | 173 | """ | |
@@ -193,6 +193,8 @@ def iter_change_type(self, change_type): | |||
| 193 | 193 | yield diff | |
| 194 | 194 | elif change_type == "D" and diff.deleted_file: | |
| 195 | 195 | yield diff | |
| 196 | + elif change_type == "C" and diff.copied_file: | ||
| 197 | + yield diff | ||
| 196 | 198 | elif change_type == "R" and diff.renamed: | |
| 197 | 199 | yield diff | |
| 198 | 200 | elif change_type == "M" and diff.a_blob and diff.b_blob and diff.a_blob != diff.b_blob: | |
@@ -235,14 +237,17 @@ class Diff(object): | |||
| 235 | 237 | # precompiled regex | |
| 236 | 238 | re_header = re.compile(br""" | |
| 237 | 239 | ^diff[ ]--git | |
| 238 | - [ ](?P<a_path_fallback>"?a/.+?"?)[ ](?P<b_path_fallback>"?b/.+?"?)\n | ||
| 240 | + [ ](?P<a_path_fallback>"?[ab]/.+?"?)[ ](?P<b_path_fallback>"?[ab]/.+?"?)\n | ||
| 239 | 241 | (?:^old[ ]mode[ ](?P<old_mode>\d+)\n | |
| 240 | 242 | ^new[ ]mode[ ](?P<new_mode>\d+)(?:\n|$))? | |
| 241 | 243 | (?:^similarity[ ]index[ ]\d+%\n | |
| 242 | 244 | ^rename[ ]from[ ](?P<rename_from>.*)\n | |
| 243 | 245 | ^rename[ ]to[ ](?P<rename_to>.*)(?:\n|$))? | |
| 244 | 246 | (?:^new[ ]file[ ]mode[ ](?P<new_file_mode>.+)(?:\n|$))? | |
| 245 | 247 | (?:^deleted[ ]file[ ]mode[ ](?P<deleted_file_mode>.+)(?:\n|$))? | |
| 248 | + (?:^similarity[ ]index[ ]\d+%\n | ||
| 249 | + ^copy[ ]from[ ].*\n | ||
| 250 | + ^copy[ ]to[ ](?P<copied_file_name>.*)(?:\n|$))? | ||
| 246 | 251 | (?:^index[ ](?P<a_blob_id>[0-9A-Fa-f]+) | |
| 247 | 252 | \.\.(?P<b_blob_id>[0-9A-Fa-f]+)[ ]?(?P<b_mode>.+)?(?:\n|$))? | |
| 248 | 253 | (?:^---[ ](?P<a_path>[^\t\n\r\f\v]*)[\t\r\f\v]*(?:\n|$))? | |
@@ -253,11 +258,11 @@ class Diff(object): | |||
| 253 | 258 | NULL_BIN_SHA = b"\0" * 20 | |
| 254 | 259 | ||
| 255 | 260 | __slots__ = ("a_blob", "b_blob", "a_mode", "b_mode", "a_rawpath", "b_rawpath", | |
| 256 | - "new_file", "deleted_file", "raw_rename_from", "raw_rename_to", | ||
| 257 | - "diff", "change_type", "score") | ||
| 261 | + "new_file", "deleted_file", "copied_file", "raw_rename_from", | ||
| 262 | + "raw_rename_to", "diff", "change_type", "score") | ||
| 258 | 263 | ||
| 259 | 264 | def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode, | |
| 260 | - b_mode, new_file, deleted_file, raw_rename_from, | ||
| 265 | + b_mode, new_file, deleted_file, copied_file, raw_rename_from, | ||
| 261 | 266 | raw_rename_to, diff, change_type, score): | |
| 262 | 267 | ||
| 263 | 268 | self.a_mode = a_mode | |
@@ -273,6 +278,14 @@ def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode, | |||
| 273 | 278 | if self.b_mode: | |
| 274 | 279 | self.b_mode = mode_str_to_int(self.b_mode) | |
| 275 | 280 | ||
| 281 | + # Determine whether this diff references a submodule, if it does then | ||
| 282 | + # we need to overwrite "repo" to the corresponding submodule's repo instead | ||
| 283 | + if repo and a_rawpath: | ||
| 284 | + for submodule in repo.submodules: | ||
| 285 | + if submodule.path == a_rawpath.decode("utf-8"): | ||
| 286 | + repo = submodule.module() | ||
| 287 | + break | ||
| 288 | + | ||
| 276 | 289 | if a_blob_id is None or a_blob_id == self.NULL_HEX_SHA: | |
| 277 | 290 | self.a_blob = None | |
| 278 | 291 | else: | |
@@ -285,6 +298,7 @@ def __init__(self, repo, a_rawpath, b_rawpath, a_blob_id, b_blob_id, a_mode, | |||
| 285 | 298 | ||
| 286 | 299 | self.new_file = new_file | |
| 287 | 300 | self.deleted_file = deleted_file | |
| 301 | + self.copied_file = copied_file | ||
| 288 | 302 | ||
| 289 | 303 | # be clear and use None instead of empty strings | |
| 290 | 304 | assert raw_rename_from is None or isinstance(raw_rename_from, binary_type) | |
@@ -336,6 +350,8 @@ def __str__(self): | |||
| 336 | 350 | msg += '\nfile deleted in rhs' | |
| 337 | 351 | if self.new_file: | |
| 338 | 352 | msg += '\nfile added in rhs' | |
| 353 | + if self.copied_file: | ||
| 354 | + msg += '\nfile %r copied from %r' % (self.b_path, self.a_path) | ||
| 339 | 355 | if self.rename_from: | |
| 340 | 356 | msg += '\nfile renamed from %r' % self.rename_from | |
| 341 | 357 | if self.rename_to: | |
@@ -420,11 +436,12 @@ def _index_from_patch_format(cls, repo, proc): | |||
| 420 | 436 | a_path_fallback, b_path_fallback, \ | |
| 421 | 437 | old_mode, new_mode, \ | |
| 422 | 438 | rename_from, rename_to, \ | |
| 423 | - new_file_mode, deleted_file_mode, \ | ||
| 439 | + new_file_mode, deleted_file_mode, copied_file_name, \ | ||
| 424 | 440 | a_blob_id, b_blob_id, b_mode, \ | |
| 425 | 441 | a_path, b_path = _header.groups() | |
| 426 | 442 | ||
| 427 | - new_file, deleted_file = bool(new_file_mode), bool(deleted_file_mode) | ||
| 443 | + new_file, deleted_file, copied_file = \ | ||
| 444 | + bool(new_file_mode), bool(deleted_file_mode), bool(copied_file_name) | ||
| 428 | 445 | ||
| 429 | 446 | a_path = cls._pick_best_path(a_path, rename_from, a_path_fallback) | |
| 430 | 447 | b_path = cls._pick_best_path(b_path, rename_to, b_path_fallback) | |
@@ -446,7 +463,7 @@ def _index_from_patch_format(cls, repo, proc): | |||
| 446 | 463 | b_blob_id and b_blob_id.decode(defenc), | |
| 447 | 464 | a_mode and a_mode.decode(defenc), | |
| 448 | 465 | b_mode and b_mode.decode(defenc), | |
| 449 | - new_file, deleted_file, | ||
| 466 | + new_file, deleted_file, copied_file, | ||
| 450 | 467 | rename_from, | |
| 451 | 468 | rename_to, | |
| 452 | 469 | None, None, None)) | |
@@ -487,6 +504,7 @@ def handle_diff_line(line): | |||
| 487 | 504 | b_path = path.encode(defenc) | |
| 488 | 505 | deleted_file = False | |
| 489 | 506 | new_file = False | |
| 507 | + copied_file = False | ||
| 490 | 508 | rename_from = None | |
| 491 | 509 | rename_to = None | |
| 492 | 510 | ||
@@ -498,6 +516,11 @@ def handle_diff_line(line): | |||
| 498 | 516 | elif change_type == 'A': | |
| 499 | 517 | a_blob_id = None | |
| 500 | 518 | new_file = True | |
| 519 | + elif change_type == 'C': | ||
| 520 | + copied_file = True | ||
| 521 | + a_path, b_path = path.split('\t', 1) | ||
| 522 | + a_path = a_path.encode(defenc) | ||
| 523 | + b_path = b_path.encode(defenc) | ||
| 501 | 524 | elif change_type == 'R': | |
| 502 | 525 | a_path, b_path = path.split('\t', 1) | |
| 503 | 526 | a_path = a_path.encode(defenc) | |
@@ -509,8 +532,8 @@ def handle_diff_line(line): | |||
| 509 | 532 | # END add/remove handling | |
| 510 | 533 | ||
| 511 | 534 | diff = Diff(repo, a_path, b_path, a_blob_id, b_blob_id, old_mode, new_mode, | |
| 512 | - new_file, deleted_file, rename_from, rename_to, '', | ||
| 513 | - change_type, score) | ||
| 535 | + new_file, deleted_file, copied_file, rename_from, rename_to, | ||
| 536 | + '', change_type, score) | ||
| 514 | 537 | index.append(diff) | |
| 515 | 538 | ||
| 516 | 539 | handle_process_output(proc, handle_diff_line, None, finalize_process, decode_streams=False) | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -105,6 +105,9 @@ def __init__(self, secs_west_of_utc, name=None): | |||
| 105 | 105 | self._offset = timedelta(seconds=-secs_west_of_utc) | |
| 106 | 106 | self._name = name or 'fixed' | |
| 107 | 107 | ||
| 108 | + def __reduce__(self): | ||
| 109 | + return tzoffset, (-self._offset.total_seconds(), self._name) | ||
| 110 | + | ||
| 108 | 111 | def utcoffset(self, dt): | |
| 109 | 112 | return self._offset | |
| 110 | 113 | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1,4 @@ | |||
| 1 | + diff --git a/test1.txt b/test2.txt | ||
| 2 | + similarity index 100% | ||
| 3 | + copy from test1.txt | ||
| 4 | + copy to test2.txt | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -0,0 +1 @@ | |||
| 1 | + :100644 100644 cfe9deac6e10683917e80f877566b58644aa21df cfe9deac6e10683917e80f877566b58644aa21df C100 test1.txt test2.txt | ||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -5,12 +5,15 @@ | |||
| 5 | 5 | # This module is part of GitPython and is released under | |
| 6 | 6 | # the BSD License: http://www.opensource.org/licenses/bsd-license.php | |
| 7 | 7 | import ddt | |
| 8 | + import shutil | ||
| 9 | + import tempfile | ||
| 8 | 10 | from git import ( | |
| 9 | 11 | Repo, | |
| 10 | 12 | GitCommandError, | |
| 11 | 13 | Diff, | |
| 12 | 14 | DiffIndex, | |
| 13 | 15 | NULL_TREE, | |
| 16 | + Submodule, | ||
| 14 | 17 | ) | |
| 15 | 18 | from git.cmd import Git | |
| 16 | 19 | from git.test.lib import ( | |
@@ -19,7 +22,6 @@ | |||
| 19 | 22 | fixture, | |
| 20 | 23 | assert_equal, | |
| 21 | 24 | assert_true, | |
| 22 | - | ||
| 23 | 25 | ) | |
| 24 | 26 | from git.test.lib import with_rw_directory | |
| 25 | 27 | ||
@@ -29,9 +31,15 @@ | |||
| 29 | 31 | @ddt.ddt | |
| 30 | 32 | class TestDiff(TestBase): | |
| 31 | 33 | ||
| 34 | + def setUp(self): | ||
| 35 | + self.repo_dir = tempfile.mkdtemp() | ||
| 36 | + self.submodule_dir = tempfile.mkdtemp() | ||
| 37 | + | ||
| 32 | 38 | def tearDown(self): | |
| 33 | 39 | import gc | |
| 34 | 40 | gc.collect() | |
| 41 | + shutil.rmtree(self.repo_dir) | ||
| 42 | + shutil.rmtree(self.submodule_dir) | ||
| 35 | 43 | ||
| 36 | 44 | def _assert_diff_format(self, diffs): | |
| 37 | 45 | # verify that the format of the diff is sane | |
@@ -60,15 +68,21 @@ def test_diff_with_staged_file(self, rw_dir): | |||
| 60 | 68 | ||
| 61 | 69 | with open(fp, 'w') as fs: | |
| 62 | 70 | fs.write("Hola Mundo") | |
| 63 | - r.git.commit(all=True, message="change on master") | ||
| 71 | + r.git.add(Git.polish_url(fp)) | ||
| 72 | + self.assertEqual(len(r.index.diff("HEAD", create_patch=True)), 1, | ||
| 73 | + "create_patch should generate patch of diff to HEAD") | ||
| 74 | + r.git.commit(message="change on master") | ||
| 75 | + self.assertEqual(len(r.index.diff("HEAD", create_patch=True)), 0, | ||
| 76 | + "create_patch should generate no patch, already on HEAD") | ||
| 64 | 77 | ||
| 65 | 78 | r.git.checkout('HEAD~1', b='topic') | |
| 66 | 79 | with open(fp, 'w') as fs: | |
| 67 | 80 | fs.write("Hallo Welt") | |
| 68 | 81 | r.git.commit(all=True, message="change on topic branch") | |
| 69 | 82 | ||
| 70 | 83 | # there must be a merge-conflict | |
| 71 | - self.failUnlessRaises(GitCommandError, r.git.cherry_pick, 'master') | ||
| 84 | + with self.assertRaises(GitCommandError): | ||
| 85 | + r.git.cherry_pick('master') | ||
| 72 | 86 | ||
| 73 | 87 | # Now do the actual testing - this should just work | |
| 74 | 88 | self.assertEqual(len(r.index.diff(None)), 2) | |
@@ -112,6 +126,29 @@ def test_diff_with_rename(self): | |||
| 112 | 126 | self.assertEqual(diff.score, 100) | |
| 113 | 127 | self.assertEqual(len(list(diffs.iter_change_type('R'))), 1) | |
| 114 | 128 | ||
| 129 | + def test_diff_with_copied_file(self): | ||
| 130 | + output = StringProcessAdapter(fixture('diff_copied_mode')) | ||
| 131 | + diffs = Diff._index_from_patch_format(self.rorepo, output) | ||
| 132 | + self._assert_diff_format(diffs) | ||
| 133 | + | ||
| 134 | + assert_equal(1, len(diffs)) | ||
| 135 | + | ||
| 136 | + diff = diffs[0] | ||
| 137 | + assert_true(diff.copied_file) | ||
| 138 | + assert_true(diff.a_path, u'test1.txt') | ||
| 139 | + assert_true(diff.b_path, u'test2.txt') | ||
| 140 | + assert isinstance(str(diff), str) | ||
| 141 | + | ||
| 142 | + output = StringProcessAdapter(fixture('diff_copied_mode_raw')) | ||
| 143 | + diffs = Diff._index_from_raw_format(self.rorepo, output) | ||
| 144 | + self.assertEqual(len(diffs), 1) | ||
| 145 | + diff = diffs[0] | ||
| 146 | + self.assertEqual(diff.change_type, 'C') | ||
| 147 | + self.assertEqual(diff.score, 100) | ||
| 148 | + self.assertEqual(diff.a_path, u'test1.txt') | ||
| 149 | + self.assertEqual(diff.b_path, u'test2.txt') | ||
| 150 | + self.assertEqual(len(list(diffs.iter_change_type('C'))), 1) | ||
| 151 | + | ||
| 115 | 152 | def test_diff_with_change_in_type(self): | |
| 116 | 153 | output = StringProcessAdapter(fixture('diff_change_in_type')) | |
| 117 | 154 | diffs = Diff._index_from_patch_format(self.rorepo, output) | |
@@ -244,6 +281,43 @@ def test_diff_with_spaces(self): | |||
| 244 | 281 | self.assertIsNone(diff_index[0].a_path, repr(diff_index[0].a_path)) | |
| 245 | 282 | self.assertEqual(diff_index[0].b_path, u'file with spaces', repr(diff_index[0].b_path)) | |
| 246 | 283 | ||
| 284 | + def test_diff_submodule(self): | ||
| 285 | + """Test that diff is able to correctly diff commits that cover submodule changes""" | ||
| 286 | + # Init a temp git repo that will be referenced as a submodule | ||
| 287 | + sub = Repo.init(self.submodule_dir) | ||
| 288 | + with open(self.submodule_dir + "/subfile", "w") as sub_subfile: | ||
| 289 | + sub_subfile.write("") | ||
| 290 | + sub.index.add(["subfile"]) | ||
| 291 | + sub.index.commit("first commit") | ||
| 292 | + | ||
| 293 | + # Init a temp git repo that will incorporate the submodule | ||
| 294 | + repo = Repo.init(self.repo_dir) | ||
| 295 | + with open(self.repo_dir + "/test", "w") as foo_test: | ||
| 296 | + foo_test.write("") | ||
| 297 | + repo.index.add(['test']) | ||
| 298 | + Submodule.add(repo, "subtest", "sub", url="file://" + self.submodule_dir) | ||
| 299 | + repo.index.commit("first commit") | ||
| 300 | + repo.create_tag('1') | ||
| 301 | + | ||
| 302 | + # Add a commit to the submodule | ||
| 303 | + submodule = repo.submodule('subtest') | ||
| 304 | + with open(self.repo_dir + "/sub/subfile", "w") as foo_sub_subfile: | ||
| 305 | + foo_sub_subfile.write("blub") | ||
| 306 | + submodule.module().index.add(["subfile"]) | ||
| 307 | + submodule.module().index.commit("changed subfile") | ||
| 308 | + submodule.binsha = submodule.module().head.commit.binsha | ||
| 309 | + | ||
| 310 | + # Commit submodule updates in parent repo | ||
| 311 | + repo.index.add([submodule]) | ||
| 312 | + repo.index.commit("submodule changed") | ||
| 313 | + repo.create_tag('2') | ||
| 314 | + | ||
| 315 | + diff = repo.commit('1').diff(repo.commit('2'))[0] | ||
| 316 | + # If diff is unable to find the commit hashes (looks in wrong repo) the *_blob.size | ||
| 317 | + # property will be a string containing exception text, an int indicates success | ||
| 318 | + self.assertIsInstance(diff.a_blob.size, int) | ||
| 319 | + self.assertIsInstance(diff.b_blob.size, int) | ||
| 320 | + | ||
| 247 | 321 | def test_diff_interface(self): | |
| 248 | 322 | # test a few variations of the main diff routine | |
| 249 | 323 | assertion_map = {} | |
| Back | FazBrowse Home | New Git URL |
0 commit comments