1 # Copyright (C) 2009 Google Inc. All rights reserved. 2 # Copyright (C) 2009 Apple Inc. All rights reserved. 3 # Copyright (C) 2011 Daniel Bates (dbates (at] intudata.com). All rights reserved. 4 # 5 # Redistribution and use in source and binary forms, with or without 6 # modification, are permitted provided that the following conditions are 7 # met: 8 # 9 # * Redistributions of source code must retain the above copyright 10 # notice, this list of conditions and the following disclaimer. 11 # * Redistributions in binary form must reproduce the above 12 # copyright notice, this list of conditions and the following disclaimer 13 # in the documentation and/or other materials provided with the 14 # distribution. 15 # * Neither the name of Google Inc. nor the names of its 16 # contributors may be used to endorse or promote products derived from 17 # this software without specific prior written permission. 18 # 19 # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 20 # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 21 # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 22 # A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 23 # OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 24 # SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 25 # LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 26 # DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 27 # THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 28 # (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 31 import atexit 32 import os 33 import shutil 34 import unittest 35 36 from webkitpy.common.system.executive import Executive, ScriptError 37 from webkitpy.common.system.executive_mock import MockExecutive 38 from webkitpy.common.system.filesystem import FileSystem 39 from webkitpy.common.system.filesystem_mock import MockFileSystem 40 from webkitpy.common.checkout.scm.detection import detect_scm_system 41 from webkitpy.common.checkout.scm.git import Git, AmbiguousCommitError 42 from webkitpy.common.checkout.scm.scm import SCM 43 from webkitpy.common.checkout.scm.svn import SVN 44 45 46 # We cache the mock SVN repo so that we don't create it again for each call to an SVNTest or GitTest test_ method. 47 # We store it in a global variable so that we can delete this cached repo on exit(3). 48 original_cwd = None 49 cached_svn_repo_path = None 50 51 @atexit.register 52 def delete_cached_svn_repo_at_exit(): 53 if cached_svn_repo_path: 54 os.chdir(original_cwd) 55 shutil.rmtree(cached_svn_repo_path) 56 57 58 class SCMTestBase(unittest.TestCase): 59 def __init__(self, *args, **kwargs): 60 super(SCMTestBase, self).__init__(*args, **kwargs) 61 self.scm = None 62 self.executive = None 63 self.fs = None 64 self.original_cwd = None 65 66 def setUp(self): 67 self.executive = Executive() 68 self.fs = FileSystem() 69 self.original_cwd = self.fs.getcwd() 70 71 def tearDown(self): 72 self._chdir(self.original_cwd) 73 74 def _join(self, *comps): 75 return self.fs.join(*comps) 76 77 def _chdir(self, path): 78 self.fs.chdir(path) 79 80 def _mkdir(self, path): 81 assert not self.fs.exists(path) 82 self.fs.maybe_make_directory(path) 83 84 def _mkdtemp(self, **kwargs): 85 return str(self.fs.mkdtemp(**kwargs)) 86 87 def _remove(self, path): 88 self.fs.remove(path) 89 90 def _rmtree(self, path): 91 self.fs.rmtree(path) 92 93 def _run(self, *args, **kwargs): 94 return self.executive.run_command(*args, **kwargs) 95 96 def _run_silent(self, args, **kwargs): 97 self.executive.run_and_throw_if_fail(args, quiet=True, **kwargs) 98 99 def _write_text_file(self, path, contents): 100 self.fs.write_text_file(path, contents) 101 102 def _write_binary_file(self, path, contents): 103 self.fs.write_binary_file(path, contents) 104 105 def _make_diff(self, command, *args): 106 # We use this wrapper to disable output decoding. diffs should be treated as 107 # binary files since they may include text files of multiple differnet encodings. 108 return self._run([command, "diff"] + list(args), decode_output=False) 109 110 def _svn_diff(self, *args): 111 return self._make_diff("svn", *args) 112 113 def _git_diff(self, *args): 114 return self._make_diff("git", *args) 115 116 def _svn_add(self, path): 117 self._run(["svn", "add", path]) 118 119 def _svn_commit(self, message): 120 self._run(["svn", "commit", "--quiet", "--message", message]) 121 122 # This is a hot function since it's invoked by unittest before calling each test_ method in SVNTest and 123 # GitTest. We create a mock SVN repo once and then perform an SVN checkout from a filesystem copy of 124 # it since it's expensive to create the mock repo. 125 def _set_up_svn_checkout(self): 126 global cached_svn_repo_path 127 global original_cwd 128 if not cached_svn_repo_path: 129 cached_svn_repo_path = self._set_up_svn_repo() 130 original_cwd = self.original_cwd 131 132 self.temp_directory = self._mkdtemp(suffix="svn_test") 133 self.svn_repo_path = self._join(self.temp_directory, "repo") 134 self.svn_repo_url = "file://%s" % self.svn_repo_path 135 self.svn_checkout_path = self._join(self.temp_directory, "checkout") 136 shutil.copytree(cached_svn_repo_path, self.svn_repo_path) 137 self._run(['svn', 'checkout', '--quiet', self.svn_repo_url + "/trunk", self.svn_checkout_path]) 138 139 def _set_up_svn_repo(self): 140 svn_repo_path = self._mkdtemp(suffix="svn_test_repo") 141 svn_repo_url = "file://%s" % svn_repo_path # Not sure this will work on windows 142 # git svn complains if we don't pass --pre-1.5-compatible, not sure why: 143 # Expected FS format '2'; found format '3' at /usr/local/libexec/git-core//git-svn line 1477 144 self._run(['svnadmin', 'create', '--pre-1.5-compatible', svn_repo_path]) 145 146 # Create a test svn checkout 147 svn_checkout_path = self._mkdtemp(suffix="svn_test_checkout") 148 self._run(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) 149 150 # Create and checkout a trunk dir to match the standard svn configuration to match git-svn's expectations 151 self._chdir(svn_checkout_path) 152 self._mkdir('trunk') 153 self._svn_add('trunk') 154 # We can add tags and branches as well if we ever need to test those. 155 self._svn_commit('add trunk') 156 157 self._rmtree(svn_checkout_path) 158 self._chdir(self.original_cwd) 159 160 self._set_up_svn_test_commits(svn_repo_url + "/trunk") 161 return svn_repo_path 162 163 def _set_up_svn_test_commits(self, svn_repo_url): 164 svn_checkout_path = self._mkdtemp(suffix="svn_test_checkout") 165 self._run(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) 166 167 # Add some test commits 168 self._chdir(svn_checkout_path) 169 170 self._write_text_file("test_file", "test1") 171 self._svn_add("test_file") 172 self._svn_commit("initial commit") 173 174 self._write_text_file("test_file", "test1test2") 175 # This used to be the last commit, but doing so broke 176 # GitTest.test_apply_git_patch which use the inverse diff of the last commit. 177 # svn-apply fails to remove directories in Git, see: 178 # https://bugs.webkit.org/show_bug.cgi?id=34871 179 self._mkdir("test_dir") 180 # Slash should always be the right path separator since we use cygwin on Windows. 181 test_file3_path = "test_dir/test_file3" 182 self._write_text_file(test_file3_path, "third file") 183 self._svn_add("test_dir") 184 self._svn_commit("second commit") 185 186 self._write_text_file("test_file", "test1test2test3\n") 187 self._write_text_file("test_file2", "second file") 188 self._svn_add("test_file2") 189 self._svn_commit("third commit") 190 191 # This 4th commit is used to make sure that our patch file handling 192 # code correctly treats patches as binary and does not attempt to 193 # decode them assuming they're utf-8. 194 self._write_binary_file("test_file", u"latin1 test: \u00A0\n".encode("latin-1")) 195 self._write_binary_file("test_file2", u"utf-8 test: \u00A0\n".encode("utf-8")) 196 self._svn_commit("fourth commit") 197 198 # svn does not seem to update after commit as I would expect. 199 self._run(['svn', 'update']) 200 self._rmtree(svn_checkout_path) 201 self._chdir(self.original_cwd) 202 203 def _tear_down_svn_checkout(self): 204 self._rmtree(self.temp_directory) 205 206 def _shared_test_add_recursively(self): 207 self._mkdir("added_dir") 208 self._write_text_file("added_dir/added_file", "new stuff") 209 self.scm.add("added_dir/added_file") 210 self.assertIn("added_dir/added_file", self.scm._added_files()) 211 212 def _shared_test_delete_recursively(self): 213 self._mkdir("added_dir") 214 self._write_text_file("added_dir/added_file", "new stuff") 215 self.scm.add("added_dir/added_file") 216 self.assertIn("added_dir/added_file", self.scm._added_files()) 217 self.scm.delete("added_dir/added_file") 218 self.assertNotIn("added_dir", self.scm._added_files()) 219 220 def _shared_test_delete_recursively_or_not(self): 221 self._mkdir("added_dir") 222 self._write_text_file("added_dir/added_file", "new stuff") 223 self._write_text_file("added_dir/another_added_file", "more new stuff") 224 self.scm.add("added_dir/added_file") 225 self.scm.add("added_dir/another_added_file") 226 self.assertIn("added_dir/added_file", self.scm._added_files()) 227 self.assertIn("added_dir/another_added_file", self.scm._added_files()) 228 self.scm.delete("added_dir/added_file") 229 self.assertIn("added_dir/another_added_file", self.scm._added_files()) 230 231 def _shared_test_exists(self, scm, commit_function): 232 self._chdir(scm.checkout_root) 233 self.assertFalse(scm.exists('foo.txt')) 234 self._write_text_file('foo.txt', 'some stuff') 235 self.assertFalse(scm.exists('foo.txt')) 236 scm.add('foo.txt') 237 commit_function('adding foo') 238 self.assertTrue(scm.exists('foo.txt')) 239 scm.delete('foo.txt') 240 commit_function('deleting foo') 241 self.assertFalse(scm.exists('foo.txt')) 242 243 def _shared_test_move(self): 244 self._write_text_file('added_file', 'new stuff') 245 self.scm.add('added_file') 246 self.scm.move('added_file', 'moved_file') 247 self.assertIn('moved_file', self.scm._added_files()) 248 249 def _shared_test_move_recursive(self): 250 self._mkdir("added_dir") 251 self._write_text_file('added_dir/added_file', 'new stuff') 252 self._write_text_file('added_dir/another_added_file', 'more new stuff') 253 self.scm.add('added_dir') 254 self.scm.move('added_dir', 'moved_dir') 255 self.assertIn('moved_dir/added_file', self.scm._added_files()) 256 self.assertIn('moved_dir/another_added_file', self.scm._added_files()) 257 258 259 class SVNTest(SCMTestBase): 260 def setUp(self): 261 super(SVNTest, self).setUp() 262 self._set_up_svn_checkout() 263 self._chdir(self.svn_checkout_path) 264 self.scm = detect_scm_system(self.svn_checkout_path) 265 self.scm.svn_server_realm = None 266 267 def tearDown(self): 268 super(SVNTest, self).tearDown() 269 self._tear_down_svn_checkout() 270 271 def test_detect_scm_system_relative_url(self): 272 scm = detect_scm_system(".") 273 # I wanted to assert that we got the right path, but there was some 274 # crazy magic with temp folder names that I couldn't figure out. 275 self.assertTrue(scm.checkout_root) 276 277 def test_detection(self): 278 self.assertEqual(self.scm.display_name(), "svn") 279 self.assertEqual(self.scm.supports_local_commits(), False) 280 281 def test_add_recursively(self): 282 self._shared_test_add_recursively() 283 284 def test_delete(self): 285 self._chdir(self.svn_checkout_path) 286 self.scm.delete("test_file") 287 self.assertIn("test_file", self.scm._deleted_files()) 288 289 def test_delete_list(self): 290 self._chdir(self.svn_checkout_path) 291 self.scm.delete_list(["test_file", "test_file2"]) 292 self.assertIn("test_file", self.scm._deleted_files()) 293 self.assertIn("test_file2", self.scm._deleted_files()) 294 295 def test_delete_recursively(self): 296 self._shared_test_delete_recursively() 297 298 def test_delete_recursively_or_not(self): 299 self._shared_test_delete_recursively_or_not() 300 301 def test_move(self): 302 self._shared_test_move() 303 304 def test_move_recursive(self): 305 self._shared_test_move_recursive() 306 307 308 class GitTest(SCMTestBase): 309 def setUp(self): 310 super(GitTest, self).setUp() 311 self._set_up_git_checkouts() 312 313 def tearDown(self): 314 super(GitTest, self).tearDown() 315 self._tear_down_git_checkouts() 316 317 def _set_up_git_checkouts(self): 318 """Sets up fresh git repository with one commit. Then sets up a second git repo that tracks the first one.""" 319 320 self.untracking_checkout_path = self._mkdtemp(suffix="git_test_checkout2") 321 self._run(['git', 'init', self.untracking_checkout_path]) 322 323 self._chdir(self.untracking_checkout_path) 324 self._write_text_file('foo_file', 'foo') 325 self._run(['git', 'add', 'foo_file']) 326 self._run(['git', 'commit', '-am', 'dummy commit']) 327 self.untracking_scm = detect_scm_system(self.untracking_checkout_path) 328 329 self.tracking_git_checkout_path = self._mkdtemp(suffix="git_test_checkout") 330 self._run(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) 331 self._chdir(self.tracking_git_checkout_path) 332 self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) 333 334 def _tear_down_git_checkouts(self): 335 self._run(['rm', '-rf', self.tracking_git_checkout_path]) 336 self._run(['rm', '-rf', self.untracking_checkout_path]) 337 338 def test_remote_branch_ref(self): 339 self.assertEqual(self.tracking_scm._remote_branch_ref(), 'refs/remotes/origin/master') 340 self._chdir(self.untracking_checkout_path) 341 self.assertRaises(ScriptError, self.untracking_scm._remote_branch_ref) 342 343 def test_multiple_remotes(self): 344 self._run(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1']) 345 self._run(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) 346 self.assertEqual(self.tracking_scm._remote_branch_ref(), 'remote1') 347 348 def test_create_patch(self): 349 self._write_text_file('test_file_commit1', 'contents') 350 self._run(['git', 'add', 'test_file_commit1']) 351 scm = self.tracking_scm 352 scm.commit_locally_with_message('message') 353 354 patch = scm.create_patch() 355 self.assertNotRegexpMatches(patch, r'Subversion Revision:') 356 357 def test_exists(self): 358 scm = self.untracking_scm 359 self._shared_test_exists(scm, scm.commit_locally_with_message) 360 361 def test_rename_files(self): 362 scm = self.tracking_scm 363 scm.move('foo_file', 'bar_file') 364 scm.commit_locally_with_message('message') 365 366 367 class GitSVNTest(SCMTestBase): 368 def setUp(self): 369 super(GitSVNTest, self).setUp() 370 self._set_up_svn_checkout() 371 self._set_up_gitsvn_checkout() 372 self.scm = detect_scm_system(self.git_checkout_path) 373 self.scm.svn_server_realm = None 374 375 def tearDown(self): 376 super(GitSVNTest, self).tearDown() 377 self._tear_down_svn_checkout() 378 self._tear_down_gitsvn_checkout() 379 380 def _set_up_gitsvn_checkout(self): 381 self.git_checkout_path = self._mkdtemp(suffix="git_test_checkout") 382 # --quiet doesn't make git svn silent 383 self._run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) 384 self._chdir(self.git_checkout_path) 385 self.git_v2 = self._run(['git', '--version']).startswith('git version 2') 386 if self.git_v2: 387 # The semantics of 'git svn clone -T' changed in v2 (apparently), so the branch names are different. 388 # This works around it, for compatibility w/ v1. 389 self._run_silent(['git', 'branch', 'trunk', 'origin/trunk']) 390 391 def _tear_down_gitsvn_checkout(self): 392 self._rmtree(self.git_checkout_path) 393 394 def test_detection(self): 395 self.assertEqual(self.scm.display_name(), "git") 396 self.assertEqual(self.scm.supports_local_commits(), True) 397 398 def test_read_git_config(self): 399 key = 'test.git-config' 400 value = 'git-config value' 401 self._run(['git', 'config', key, value]) 402 self.assertEqual(self.scm.read_git_config(key), value) 403 404 def test_local_commits(self): 405 test_file = self._join(self.git_checkout_path, 'test_file') 406 self._write_text_file(test_file, 'foo') 407 self._run(['git', 'commit', '-a', '-m', 'local commit']) 408 409 self.assertEqual(len(self.scm._local_commits()), 1) 410 411 def test_discard_local_commits(self): 412 test_file = self._join(self.git_checkout_path, 'test_file') 413 self._write_text_file(test_file, 'foo') 414 self._run(['git', 'commit', '-a', '-m', 'local commit']) 415 416 self.assertEqual(len(self.scm._local_commits()), 1) 417 self.scm._discard_local_commits() 418 self.assertEqual(len(self.scm._local_commits()), 0) 419 420 def test_delete_branch(self): 421 new_branch = 'foo' 422 423 self._run(['git', 'checkout', '-b', new_branch]) 424 self.assertEqual(self._run(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) 425 426 self._run(['git', 'checkout', '-b', 'bar']) 427 self.scm.delete_branch(new_branch) 428 429 self.assertNotRegexpMatches(self._run(['git', 'branch']), r'foo') 430 431 def test_rebase_in_progress(self): 432 svn_test_file = self._join(self.svn_checkout_path, 'test_file') 433 self._write_text_file(svn_test_file, "svn_checkout") 434 self._run(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) 435 436 git_test_file = self._join(self.git_checkout_path, 'test_file') 437 self._write_text_file(git_test_file, "git_checkout") 438 self._run(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) 439 440 # Should fail due to a conflict leaving us mid-rebase. 441 # we use self._run_slient because --quiet doesn't actually make git svn silent. 442 self.assertRaises(ScriptError, self._run_silent, ['git', 'svn', '--quiet', 'rebase']) 443 444 self.assertTrue(self.scm._rebase_in_progress()) 445 446 # Make sure our cleanup works. 447 self.scm._discard_working_directory_changes() 448 self.assertFalse(self.scm._rebase_in_progress()) 449 450 # Make sure cleanup doesn't throw when no rebase is in progress. 451 self.scm._discard_working_directory_changes() 452 453 def _local_commit(self, filename, contents, message): 454 self._write_text_file(filename, contents) 455 self._run(['git', 'add', filename]) 456 self.scm.commit_locally_with_message(message) 457 458 def _one_local_commit(self): 459 self._local_commit('test_file_commit1', 'more test content', 'another test commit') 460 461 def _one_local_commit_plus_working_copy_changes(self): 462 self._one_local_commit() 463 self._write_text_file('test_file_commit2', 'still more test content') 464 self._run(['git', 'add', 'test_file_commit2']) 465 466 def _second_local_commit(self): 467 self._local_commit('test_file_commit2', 'still more test content', 'yet another test commit') 468 469 def _two_local_commits(self): 470 self._one_local_commit() 471 self._second_local_commit() 472 473 def _three_local_commits(self): 474 self._local_commit('test_file_commit0', 'more test content', 'another test commit') 475 self._two_local_commits() 476 477 def test_locally_commit_all_working_copy_changes(self): 478 self._local_commit('test_file', 'test content', 'test commit') 479 self._write_text_file('test_file', 'changed test content') 480 self.assertTrue(self.scm.has_working_directory_changes()) 481 self.scm.commit_locally_with_message('all working copy changes') 482 self.assertFalse(self.scm.has_working_directory_changes()) 483 484 def test_locally_commit_no_working_copy_changes(self): 485 self._local_commit('test_file', 'test content', 'test commit') 486 self._write_text_file('test_file', 'changed test content') 487 self.assertTrue(self.scm.has_working_directory_changes()) 488 self.assertRaises(ScriptError, self.scm.commit_locally_with_message, 'no working copy changes', False) 489 490 def _test_upstream_branch(self): 491 self._run(['git', 'checkout', '-t', '-b', 'my-branch']) 492 self._run(['git', 'checkout', '-t', '-b', 'my-second-branch']) 493 self.assertEqual(self.scm._upstream_branch(), 'my-branch') 494 495 def test_remote_branch_ref(self): 496 remote_branch_ref = self.scm._remote_branch_ref() 497 if self.git_v2: 498 self.assertEqual(remote_branch_ref, 'refs/remotes/origin/trunk') 499 else: 500 self.assertEqual(remote_branch_ref, 'refs/remotes/trunk') 501 502 def test_create_patch_local_plus_working_copy(self): 503 self._one_local_commit_plus_working_copy_changes() 504 patch = self.scm.create_patch() 505 self.assertRegexpMatches(patch, r'test_file_commit1') 506 self.assertRegexpMatches(patch, r'test_file_commit2') 507 508 def test_create_patch(self): 509 self._one_local_commit_plus_working_copy_changes() 510 patch = self.scm.create_patch() 511 self.assertRegexpMatches(patch, r'test_file_commit2') 512 self.assertRegexpMatches(patch, r'test_file_commit1') 513 self.assertRegexpMatches(patch, r'Subversion Revision: 5') 514 515 def test_create_patch_after_merge(self): 516 self._run(['git', 'checkout', '-b', 'dummy-branch', 'trunk~3']) 517 self._one_local_commit() 518 self._run(['git', 'merge', 'trunk']) 519 520 patch = self.scm.create_patch() 521 self.assertRegexpMatches(patch, r'test_file_commit1') 522 self.assertRegexpMatches(patch, r'Subversion Revision: 5') 523 524 def test_create_patch_with_changed_files(self): 525 self._one_local_commit_plus_working_copy_changes() 526 patch = self.scm.create_patch(changed_files=['test_file_commit2']) 527 self.assertRegexpMatches(patch, r'test_file_commit2') 528 529 def test_create_patch_with_rm_and_changed_files(self): 530 self._one_local_commit_plus_working_copy_changes() 531 self._remove('test_file_commit1') 532 patch = self.scm.create_patch() 533 patch_with_changed_files = self.scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2']) 534 self.assertEqual(patch, patch_with_changed_files) 535 536 def test_create_patch_git_commit(self): 537 self._two_local_commits() 538 patch = self.scm.create_patch(git_commit="HEAD^") 539 self.assertRegexpMatches(patch, r'test_file_commit1') 540 self.assertNotRegexpMatches(patch, r'test_file_commit2') 541 542 def test_create_patch_git_commit_range(self): 543 self._three_local_commits() 544 patch = self.scm.create_patch(git_commit="HEAD~2..HEAD") 545 self.assertNotRegexpMatches(patch, r'test_file_commit0') 546 self.assertRegexpMatches(patch, r'test_file_commit2') 547 self.assertRegexpMatches(patch, r'test_file_commit1') 548 549 def test_create_patch_working_copy_only(self): 550 self._one_local_commit_plus_working_copy_changes() 551 patch = self.scm.create_patch(git_commit="HEAD....") 552 self.assertNotRegexpMatches(patch, r'test_file_commit1') 553 self.assertRegexpMatches(patch, r'test_file_commit2') 554 555 def test_create_patch_multiple_local_commits(self): 556 self._two_local_commits() 557 patch = self.scm.create_patch() 558 self.assertRegexpMatches(patch, r'test_file_commit2') 559 self.assertRegexpMatches(patch, r'test_file_commit1') 560 561 def test_create_patch_not_synced(self): 562 self._run(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 563 self._two_local_commits() 564 patch = self.scm.create_patch() 565 self.assertNotRegexpMatches(patch, r'test_file2') 566 self.assertRegexpMatches(patch, r'test_file_commit2') 567 self.assertRegexpMatches(patch, r'test_file_commit1') 568 569 def test_create_binary_patch(self): 570 # Create a git binary patch and check the contents. 571 test_file_name = 'binary_file' 572 test_file_path = self.fs.join(self.git_checkout_path, test_file_name) 573 file_contents = ''.join(map(chr, range(256))) 574 self._write_binary_file(test_file_path, file_contents) 575 self._run(['git', 'add', test_file_name]) 576 patch = self.scm.create_patch() 577 self.assertRegexpMatches(patch, r'\nliteral 0\n') 578 self.assertRegexpMatches(patch, r'\nliteral 256\n') 579 580 # Check if we can create a patch from a local commit. 581 self._write_binary_file(test_file_path, file_contents) 582 self._run(['git', 'add', test_file_name]) 583 self._run(['git', 'commit', '-m', 'binary diff']) 584 585 patch_from_local_commit = self.scm.create_patch('HEAD') 586 self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 0\n') 587 self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 256\n') 588 589 590 def test_changed_files_local_plus_working_copy(self): 591 self._one_local_commit_plus_working_copy_changes() 592 files = self.scm.changed_files() 593 self.assertIn('test_file_commit1', files) 594 self.assertIn('test_file_commit2', files) 595 596 # working copy should *not* be in the list. 597 files = self.scm.changed_files('trunk..') 598 self.assertIn('test_file_commit1', files) 599 self.assertNotIn('test_file_commit2', files) 600 601 # working copy *should* be in the list. 602 files = self.scm.changed_files('trunk....') 603 self.assertIn('test_file_commit1', files) 604 self.assertIn('test_file_commit2', files) 605 606 def test_changed_files_git_commit(self): 607 self._two_local_commits() 608 files = self.scm.changed_files(git_commit="HEAD^") 609 self.assertIn('test_file_commit1', files) 610 self.assertNotIn('test_file_commit2', files) 611 612 def test_changed_files_git_commit_range(self): 613 self._three_local_commits() 614 files = self.scm.changed_files(git_commit="HEAD~2..HEAD") 615 self.assertNotIn('test_file_commit0', files) 616 self.assertIn('test_file_commit1', files) 617 self.assertIn('test_file_commit2', files) 618 619 def test_changed_files_working_copy_only(self): 620 self._one_local_commit_plus_working_copy_changes() 621 files = self.scm.changed_files(git_commit="HEAD....") 622 self.assertNotIn('test_file_commit1', files) 623 self.assertIn('test_file_commit2', files) 624 625 def test_changed_files_multiple_local_commits(self): 626 self._two_local_commits() 627 files = self.scm.changed_files() 628 self.assertIn('test_file_commit2', files) 629 self.assertIn('test_file_commit1', files) 630 631 def test_changed_files_not_synced(self): 632 self._run(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 633 self._two_local_commits() 634 files = self.scm.changed_files() 635 self.assertNotIn('test_file2', files) 636 self.assertIn('test_file_commit2', files) 637 self.assertIn('test_file_commit1', files) 638 639 def test_changed_files_upstream(self): 640 self._run(['git', 'checkout', '-t', '-b', 'my-branch']) 641 self._one_local_commit() 642 self._run(['git', 'checkout', '-t', '-b', 'my-second-branch']) 643 self._second_local_commit() 644 self._write_text_file('test_file_commit0', 'more test content') 645 self._run(['git', 'add', 'test_file_commit0']) 646 647 # equivalent to 'git diff my-branch..HEAD, should not include working changes 648 files = self.scm.changed_files(git_commit='UPSTREAM..') 649 self.assertNotIn('test_file_commit1', files) 650 self.assertIn('test_file_commit2', files) 651 self.assertNotIn('test_file_commit0', files) 652 653 # equivalent to 'git diff my-branch', *should* include working changes 654 files = self.scm.changed_files(git_commit='UPSTREAM....') 655 self.assertNotIn('test_file_commit1', files) 656 self.assertIn('test_file_commit2', files) 657 self.assertIn('test_file_commit0', files) 658 659 def test_add_recursively(self): 660 self._shared_test_add_recursively() 661 662 def test_delete(self): 663 self._two_local_commits() 664 self.scm.delete('test_file_commit1') 665 self.assertIn("test_file_commit1", self.scm._deleted_files()) 666 667 def test_delete_list(self): 668 self._two_local_commits() 669 self.scm.delete_list(["test_file_commit1", "test_file_commit2"]) 670 self.assertIn("test_file_commit1", self.scm._deleted_files()) 671 self.assertIn("test_file_commit2", self.scm._deleted_files()) 672 673 def test_delete_recursively(self): 674 self._shared_test_delete_recursively() 675 676 def test_delete_recursively_or_not(self): 677 self._shared_test_delete_recursively_or_not() 678 679 def test_move(self): 680 self._shared_test_move() 681 682 def test_move_recursive(self): 683 self._shared_test_move_recursive() 684 685 def test_exists(self): 686 self._shared_test_exists(self.scm, self.scm.commit_locally_with_message) 687 688 689 class GitTestWithMock(SCMTestBase): 690 def make_scm(self): 691 scm = Git(cwd=".", executive=MockExecutive(), filesystem=MockFileSystem()) 692 scm.read_git_config = lambda *args, **kw: "MOCKKEY:MOCKVALUE" 693 return scm 694 695 def test_timestamp_of_revision(self): 696 scm = self.make_scm() 697 scm.find_checkout_root = lambda path: '' 698 scm._run_git = lambda args: 'Date: 2013-02-08 08:05:49 +0000' 699 self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-08T08:05:49Z') 700 701 scm._run_git = lambda args: 'Date: 2013-02-08 01:02:03 +0130' 702 self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-07T23:32:03Z') 703 704 scm._run_git = lambda args: 'Date: 2013-02-08 01:55:21 -0800' 705 self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-08T09:55:21Z') 706