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@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 31import atexit 32import os 33import shutil 34 35from webkitpy.common.system.executive import Executive, ScriptError 36from webkitpy.common.system.executive_mock import MockExecutive 37from webkitpy.common.system.filesystem import FileSystem 38from webkitpy.common.system.filesystem_mock import MockFileSystem 39from webkitpy.common.checkout.scm.detection import detect_scm_system 40from webkitpy.common.checkout.scm.git import Git, AmbiguousCommitError 41from webkitpy.common.checkout.scm.scm import SCM 42from webkitpy.common.checkout.scm.svn import SVN 43import webkitpy.thirdparty.unittest2 as unittest 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). 48original_cwd = None 49cached_svn_repo_path = None 50 51@atexit.register 52def 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 58class 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 159 self._set_up_svn_test_commits(svn_repo_url + "/trunk") 160 return svn_repo_path 161 162 def _set_up_svn_test_commits(self, svn_repo_url): 163 svn_checkout_path = self._mkdtemp(suffix="svn_test_checkout") 164 self._run(['svn', 'checkout', '--quiet', svn_repo_url, svn_checkout_path]) 165 166 # Add some test commits 167 self._chdir(svn_checkout_path) 168 169 self._write_text_file("test_file", "test1") 170 self._svn_add("test_file") 171 self._svn_commit("initial commit") 172 173 self._write_text_file("test_file", "test1test2") 174 # This used to be the last commit, but doing so broke 175 # GitTest.test_apply_git_patch which use the inverse diff of the last commit. 176 # svn-apply fails to remove directories in Git, see: 177 # https://bugs.webkit.org/show_bug.cgi?id=34871 178 self._mkdir("test_dir") 179 # Slash should always be the right path separator since we use cygwin on Windows. 180 test_file3_path = "test_dir/test_file3" 181 self._write_text_file(test_file3_path, "third file") 182 self._svn_add("test_dir") 183 self._svn_commit("second commit") 184 185 self._write_text_file("test_file", "test1test2test3\n") 186 self._write_text_file("test_file2", "second file") 187 self._svn_add("test_file2") 188 self._svn_commit("third commit") 189 190 # This 4th commit is used to make sure that our patch file handling 191 # code correctly treats patches as binary and does not attempt to 192 # decode them assuming they're utf-8. 193 self._write_binary_file("test_file", u"latin1 test: \u00A0\n".encode("latin-1")) 194 self._write_binary_file("test_file2", u"utf-8 test: \u00A0\n".encode("utf-8")) 195 self._svn_commit("fourth commit") 196 197 # svn does not seem to update after commit as I would expect. 198 self._run(['svn', 'update']) 199 self._rmtree(svn_checkout_path) 200 201 def _tear_down_svn_checkout(self): 202 self._rmtree(self.temp_directory) 203 204 def _shared_test_add_recursively(self): 205 self._mkdir("added_dir") 206 self._write_text_file("added_dir/added_file", "new stuff") 207 self.scm.add("added_dir/added_file") 208 self.assertIn("added_dir/added_file", self.scm._added_files()) 209 210 def _shared_test_delete_recursively(self): 211 self._mkdir("added_dir") 212 self._write_text_file("added_dir/added_file", "new stuff") 213 self.scm.add("added_dir/added_file") 214 self.assertIn("added_dir/added_file", self.scm._added_files()) 215 self.scm.delete("added_dir/added_file") 216 self.assertNotIn("added_dir", self.scm._added_files()) 217 218 def _shared_test_delete_recursively_or_not(self): 219 self._mkdir("added_dir") 220 self._write_text_file("added_dir/added_file", "new stuff") 221 self._write_text_file("added_dir/another_added_file", "more new stuff") 222 self.scm.add("added_dir/added_file") 223 self.scm.add("added_dir/another_added_file") 224 self.assertIn("added_dir/added_file", self.scm._added_files()) 225 self.assertIn("added_dir/another_added_file", self.scm._added_files()) 226 self.scm.delete("added_dir/added_file") 227 self.assertIn("added_dir/another_added_file", self.scm._added_files()) 228 229 def _shared_test_exists(self, scm, commit_function): 230 self._chdir(scm.checkout_root) 231 self.assertFalse(scm.exists('foo.txt')) 232 self._write_text_file('foo.txt', 'some stuff') 233 self.assertFalse(scm.exists('foo.txt')) 234 scm.add('foo.txt') 235 commit_function('adding foo') 236 self.assertTrue(scm.exists('foo.txt')) 237 scm.delete('foo.txt') 238 commit_function('deleting foo') 239 self.assertFalse(scm.exists('foo.txt')) 240 241 def _shared_test_move(self): 242 self._write_text_file('added_file', 'new stuff') 243 self.scm.add('added_file') 244 self.scm.move('added_file', 'moved_file') 245 self.assertIn('moved_file', self.scm._added_files()) 246 247 def _shared_test_move_recursive(self): 248 self._mkdir("added_dir") 249 self._write_text_file('added_dir/added_file', 'new stuff') 250 self._write_text_file('added_dir/another_added_file', 'more new stuff') 251 self.scm.add('added_dir') 252 self.scm.move('added_dir', 'moved_dir') 253 self.assertIn('moved_dir/added_file', self.scm._added_files()) 254 self.assertIn('moved_dir/another_added_file', self.scm._added_files()) 255 256 257class SVNTest(SCMTestBase): 258 def setUp(self): 259 super(SVNTest, self).setUp() 260 self._set_up_svn_checkout() 261 self._chdir(self.svn_checkout_path) 262 self.scm = detect_scm_system(self.svn_checkout_path) 263 self.scm.svn_server_realm = None 264 265 def tearDown(self): 266 super(SVNTest, self).tearDown() 267 self._tear_down_svn_checkout() 268 269 def test_detect_scm_system_relative_url(self): 270 scm = detect_scm_system(".") 271 # I wanted to assert that we got the right path, but there was some 272 # crazy magic with temp folder names that I couldn't figure out. 273 self.assertTrue(scm.checkout_root) 274 275 def test_detection(self): 276 self.assertEqual(self.scm.display_name(), "svn") 277 self.assertEqual(self.scm.supports_local_commits(), False) 278 279 def test_add_recursively(self): 280 self._shared_test_add_recursively() 281 282 def test_delete(self): 283 self._chdir(self.svn_checkout_path) 284 self.scm.delete("test_file") 285 self.assertIn("test_file", self.scm._deleted_files()) 286 287 def test_delete_list(self): 288 self._chdir(self.svn_checkout_path) 289 self.scm.delete_list(["test_file", "test_file2"]) 290 self.assertIn("test_file", self.scm._deleted_files()) 291 self.assertIn("test_file2", self.scm._deleted_files()) 292 293 def test_delete_recursively(self): 294 self._shared_test_delete_recursively() 295 296 def test_delete_recursively_or_not(self): 297 self._shared_test_delete_recursively_or_not() 298 299 def test_move(self): 300 self._shared_test_move() 301 302 def test_move_recursive(self): 303 self._shared_test_move_recursive() 304 305 306class GitTest(SCMTestBase): 307 def setUp(self): 308 super(GitTest, self).setUp() 309 self._set_up_git_checkouts() 310 311 def tearDown(self): 312 super(GitTest, self).tearDown() 313 self._tear_down_git_checkouts() 314 315 def _set_up_git_checkouts(self): 316 """Sets up fresh git repository with one commit. Then sets up a second git repo that tracks the first one.""" 317 318 self.untracking_checkout_path = self._mkdtemp(suffix="git_test_checkout2") 319 self._run(['git', 'init', self.untracking_checkout_path]) 320 321 self._chdir(self.untracking_checkout_path) 322 self._write_text_file('foo_file', 'foo') 323 self._run(['git', 'add', 'foo_file']) 324 self._run(['git', 'commit', '-am', 'dummy commit']) 325 self.untracking_scm = detect_scm_system(self.untracking_checkout_path) 326 327 self.tracking_git_checkout_path = self._mkdtemp(suffix="git_test_checkout") 328 self._run(['git', 'clone', '--quiet', self.untracking_checkout_path, self.tracking_git_checkout_path]) 329 self._chdir(self.tracking_git_checkout_path) 330 self.tracking_scm = detect_scm_system(self.tracking_git_checkout_path) 331 332 def _tear_down_git_checkouts(self): 333 self._run(['rm', '-rf', self.tracking_git_checkout_path]) 334 self._run(['rm', '-rf', self.untracking_checkout_path]) 335 336 def test_remote_branch_ref(self): 337 self.assertEqual(self.tracking_scm._remote_branch_ref(), 'refs/remotes/origin/master') 338 self._chdir(self.untracking_checkout_path) 339 self.assertRaises(ScriptError, self.untracking_scm._remote_branch_ref) 340 341 def test_multiple_remotes(self): 342 self._run(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote1']) 343 self._run(['git', 'config', '--add', 'svn-remote.svn.fetch', 'trunk:remote2']) 344 self.assertEqual(self.tracking_scm._remote_branch_ref(), 'remote1') 345 346 def test_create_patch(self): 347 self._write_text_file('test_file_commit1', 'contents') 348 self._run(['git', 'add', 'test_file_commit1']) 349 scm = self.tracking_scm 350 scm.commit_locally_with_message('message') 351 352 patch = scm.create_patch() 353 self.assertNotRegexpMatches(patch, r'Subversion Revision:') 354 355 def test_exists(self): 356 scm = self.untracking_scm 357 self._shared_test_exists(scm, scm.commit_locally_with_message) 358 359 def test_rename_files(self): 360 scm = self.tracking_scm 361 scm.move('foo_file', 'bar_file') 362 scm.commit_locally_with_message('message') 363 364 365class GitSVNTest(SCMTestBase): 366 def setUp(self): 367 super(GitSVNTest, self).setUp() 368 self._set_up_svn_checkout() 369 self._set_up_gitsvn_checkout() 370 self.scm = detect_scm_system(self.git_checkout_path) 371 self.scm.svn_server_realm = None 372 373 def tearDown(self): 374 super(GitSVNTest, self).tearDown() 375 self._tear_down_svn_checkout() 376 self._tear_down_gitsvn_checkout() 377 378 def _set_up_gitsvn_checkout(self): 379 self.git_checkout_path = self._mkdtemp(suffix="git_test_checkout") 380 # --quiet doesn't make git svn silent 381 self._run_silent(['git', 'svn', 'clone', '-T', 'trunk', self.svn_repo_url, self.git_checkout_path]) 382 self._chdir(self.git_checkout_path) 383 384 def _tear_down_gitsvn_checkout(self): 385 self._rmtree(self.git_checkout_path) 386 387 def test_detection(self): 388 self.assertEqual(self.scm.display_name(), "git") 389 self.assertEqual(self.scm.supports_local_commits(), True) 390 391 def test_read_git_config(self): 392 key = 'test.git-config' 393 value = 'git-config value' 394 self._run(['git', 'config', key, value]) 395 self.assertEqual(self.scm.read_git_config(key), value) 396 397 def test_local_commits(self): 398 test_file = self._join(self.git_checkout_path, 'test_file') 399 self._write_text_file(test_file, 'foo') 400 self._run(['git', 'commit', '-a', '-m', 'local commit']) 401 402 self.assertEqual(len(self.scm._local_commits()), 1) 403 404 def test_discard_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 self.scm._discard_local_commits() 411 self.assertEqual(len(self.scm._local_commits()), 0) 412 413 def test_delete_branch(self): 414 new_branch = 'foo' 415 416 self._run(['git', 'checkout', '-b', new_branch]) 417 self.assertEqual(self._run(['git', 'symbolic-ref', 'HEAD']).strip(), 'refs/heads/' + new_branch) 418 419 self._run(['git', 'checkout', '-b', 'bar']) 420 self.scm.delete_branch(new_branch) 421 422 self.assertNotRegexpMatches(self._run(['git', 'branch']), r'foo') 423 424 def test_rebase_in_progress(self): 425 svn_test_file = self._join(self.svn_checkout_path, 'test_file') 426 self._write_text_file(svn_test_file, "svn_checkout") 427 self._run(['svn', 'commit', '--message', 'commit to conflict with git commit'], cwd=self.svn_checkout_path) 428 429 git_test_file = self._join(self.git_checkout_path, 'test_file') 430 self._write_text_file(git_test_file, "git_checkout") 431 self._run(['git', 'commit', '-a', '-m', 'commit to be thrown away by rebase abort']) 432 433 # Should fail due to a conflict leaving us mid-rebase. 434 # we use self._run_slient because --quiet doesn't actually make git svn silent. 435 self.assertRaises(ScriptError, self._run_silent, ['git', 'svn', '--quiet', 'rebase']) 436 437 self.assertTrue(self.scm._rebase_in_progress()) 438 439 # Make sure our cleanup works. 440 self.scm._discard_working_directory_changes() 441 self.assertFalse(self.scm._rebase_in_progress()) 442 443 # Make sure cleanup doesn't throw when no rebase is in progress. 444 self.scm._discard_working_directory_changes() 445 446 def _local_commit(self, filename, contents, message): 447 self._write_text_file(filename, contents) 448 self._run(['git', 'add', filename]) 449 self.scm.commit_locally_with_message(message) 450 451 def _one_local_commit(self): 452 self._local_commit('test_file_commit1', 'more test content', 'another test commit') 453 454 def _one_local_commit_plus_working_copy_changes(self): 455 self._one_local_commit() 456 self._write_text_file('test_file_commit2', 'still more test content') 457 self._run(['git', 'add', 'test_file_commit2']) 458 459 def _second_local_commit(self): 460 self._local_commit('test_file_commit2', 'still more test content', 'yet another test commit') 461 462 def _two_local_commits(self): 463 self._one_local_commit() 464 self._second_local_commit() 465 466 def _three_local_commits(self): 467 self._local_commit('test_file_commit0', 'more test content', 'another test commit') 468 self._two_local_commits() 469 470 def test_locally_commit_all_working_copy_changes(self): 471 self._local_commit('test_file', 'test content', 'test commit') 472 self._write_text_file('test_file', 'changed test content') 473 self.assertTrue(self.scm.has_working_directory_changes()) 474 self.scm.commit_locally_with_message('all working copy changes') 475 self.assertFalse(self.scm.has_working_directory_changes()) 476 477 def test_locally_commit_no_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.assertRaises(ScriptError, self.scm.commit_locally_with_message, 'no working copy changes', False) 482 483 def _test_upstream_branch(self): 484 self._run(['git', 'checkout', '-t', '-b', 'my-branch']) 485 self._run(['git', 'checkout', '-t', '-b', 'my-second-branch']) 486 self.assertEqual(self.scm._upstream_branch(), 'my-branch') 487 488 def test_remote_branch_ref(self): 489 self.assertEqual(self.scm._remote_branch_ref(), 'refs/remotes/trunk') 490 491 def test_create_patch_local_plus_working_copy(self): 492 self._one_local_commit_plus_working_copy_changes() 493 patch = self.scm.create_patch() 494 self.assertRegexpMatches(patch, r'test_file_commit1') 495 self.assertRegexpMatches(patch, r'test_file_commit2') 496 497 def test_create_patch(self): 498 self._one_local_commit_plus_working_copy_changes() 499 patch = self.scm.create_patch() 500 self.assertRegexpMatches(patch, r'test_file_commit2') 501 self.assertRegexpMatches(patch, r'test_file_commit1') 502 self.assertRegexpMatches(patch, r'Subversion Revision: 5') 503 504 def test_create_patch_after_merge(self): 505 self._run(['git', 'checkout', '-b', 'dummy-branch', 'trunk~3']) 506 self._one_local_commit() 507 self._run(['git', 'merge', 'trunk']) 508 509 patch = self.scm.create_patch() 510 self.assertRegexpMatches(patch, r'test_file_commit1') 511 self.assertRegexpMatches(patch, r'Subversion Revision: 5') 512 513 def test_create_patch_with_changed_files(self): 514 self._one_local_commit_plus_working_copy_changes() 515 patch = self.scm.create_patch(changed_files=['test_file_commit2']) 516 self.assertRegexpMatches(patch, r'test_file_commit2') 517 518 def test_create_patch_with_rm_and_changed_files(self): 519 self._one_local_commit_plus_working_copy_changes() 520 self._remove('test_file_commit1') 521 patch = self.scm.create_patch() 522 patch_with_changed_files = self.scm.create_patch(changed_files=['test_file_commit1', 'test_file_commit2']) 523 self.assertEqual(patch, patch_with_changed_files) 524 525 def test_create_patch_git_commit(self): 526 self._two_local_commits() 527 patch = self.scm.create_patch(git_commit="HEAD^") 528 self.assertRegexpMatches(patch, r'test_file_commit1') 529 self.assertNotRegexpMatches(patch, r'test_file_commit2') 530 531 def test_create_patch_git_commit_range(self): 532 self._three_local_commits() 533 patch = self.scm.create_patch(git_commit="HEAD~2..HEAD") 534 self.assertNotRegexpMatches(patch, r'test_file_commit0') 535 self.assertRegexpMatches(patch, r'test_file_commit2') 536 self.assertRegexpMatches(patch, r'test_file_commit1') 537 538 def test_create_patch_working_copy_only(self): 539 self._one_local_commit_plus_working_copy_changes() 540 patch = self.scm.create_patch(git_commit="HEAD....") 541 self.assertNotRegexpMatches(patch, r'test_file_commit1') 542 self.assertRegexpMatches(patch, r'test_file_commit2') 543 544 def test_create_patch_multiple_local_commits(self): 545 self._two_local_commits() 546 patch = self.scm.create_patch() 547 self.assertRegexpMatches(patch, r'test_file_commit2') 548 self.assertRegexpMatches(patch, r'test_file_commit1') 549 550 def test_create_patch_not_synced(self): 551 self._run(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 552 self._two_local_commits() 553 patch = self.scm.create_patch() 554 self.assertNotRegexpMatches(patch, r'test_file2') 555 self.assertRegexpMatches(patch, r'test_file_commit2') 556 self.assertRegexpMatches(patch, r'test_file_commit1') 557 558 def test_create_binary_patch(self): 559 # Create a git binary patch and check the contents. 560 test_file_name = 'binary_file' 561 test_file_path = self.fs.join(self.git_checkout_path, test_file_name) 562 file_contents = ''.join(map(chr, range(256))) 563 self._write_binary_file(test_file_path, file_contents) 564 self._run(['git', 'add', test_file_name]) 565 patch = self.scm.create_patch() 566 self.assertRegexpMatches(patch, r'\nliteral 0\n') 567 self.assertRegexpMatches(patch, r'\nliteral 256\n') 568 569 # Check if we can create a patch from a local commit. 570 self._write_binary_file(test_file_path, file_contents) 571 self._run(['git', 'add', test_file_name]) 572 self._run(['git', 'commit', '-m', 'binary diff']) 573 574 patch_from_local_commit = self.scm.create_patch('HEAD') 575 self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 0\n') 576 self.assertRegexpMatches(patch_from_local_commit, r'\nliteral 256\n') 577 578 579 def test_changed_files_local_plus_working_copy(self): 580 self._one_local_commit_plus_working_copy_changes() 581 files = self.scm.changed_files() 582 self.assertIn('test_file_commit1', files) 583 self.assertIn('test_file_commit2', files) 584 585 # working copy should *not* be in the list. 586 files = self.scm.changed_files('trunk..') 587 self.assertIn('test_file_commit1', files) 588 self.assertNotIn('test_file_commit2', files) 589 590 # working copy *should* be in the list. 591 files = self.scm.changed_files('trunk....') 592 self.assertIn('test_file_commit1', files) 593 self.assertIn('test_file_commit2', files) 594 595 def test_changed_files_git_commit(self): 596 self._two_local_commits() 597 files = self.scm.changed_files(git_commit="HEAD^") 598 self.assertIn('test_file_commit1', files) 599 self.assertNotIn('test_file_commit2', files) 600 601 def test_changed_files_git_commit_range(self): 602 self._three_local_commits() 603 files = self.scm.changed_files(git_commit="HEAD~2..HEAD") 604 self.assertNotIn('test_file_commit0', files) 605 self.assertIn('test_file_commit1', files) 606 self.assertIn('test_file_commit2', files) 607 608 def test_changed_files_working_copy_only(self): 609 self._one_local_commit_plus_working_copy_changes() 610 files = self.scm.changed_files(git_commit="HEAD....") 611 self.assertNotIn('test_file_commit1', files) 612 self.assertIn('test_file_commit2', files) 613 614 def test_changed_files_multiple_local_commits(self): 615 self._two_local_commits() 616 files = self.scm.changed_files() 617 self.assertIn('test_file_commit2', files) 618 self.assertIn('test_file_commit1', files) 619 620 def test_changed_files_not_synced(self): 621 self._run(['git', 'checkout', '-b', 'my-branch', 'trunk~3']) 622 self._two_local_commits() 623 files = self.scm.changed_files() 624 self.assertNotIn('test_file2', files) 625 self.assertIn('test_file_commit2', files) 626 self.assertIn('test_file_commit1', files) 627 628 def test_changed_files_upstream(self): 629 self._run(['git', 'checkout', '-t', '-b', 'my-branch']) 630 self._one_local_commit() 631 self._run(['git', 'checkout', '-t', '-b', 'my-second-branch']) 632 self._second_local_commit() 633 self._write_text_file('test_file_commit0', 'more test content') 634 self._run(['git', 'add', 'test_file_commit0']) 635 636 # equivalent to 'git diff my-branch..HEAD, should not include working changes 637 files = self.scm.changed_files(git_commit='UPSTREAM..') 638 self.assertNotIn('test_file_commit1', files) 639 self.assertIn('test_file_commit2', files) 640 self.assertNotIn('test_file_commit0', files) 641 642 # equivalent to 'git diff my-branch', *should* include working changes 643 files = self.scm.changed_files(git_commit='UPSTREAM....') 644 self.assertNotIn('test_file_commit1', files) 645 self.assertIn('test_file_commit2', files) 646 self.assertIn('test_file_commit0', files) 647 648 def test_add_recursively(self): 649 self._shared_test_add_recursively() 650 651 def test_delete(self): 652 self._two_local_commits() 653 self.scm.delete('test_file_commit1') 654 self.assertIn("test_file_commit1", self.scm._deleted_files()) 655 656 def test_delete_list(self): 657 self._two_local_commits() 658 self.scm.delete_list(["test_file_commit1", "test_file_commit2"]) 659 self.assertIn("test_file_commit1", self.scm._deleted_files()) 660 self.assertIn("test_file_commit2", self.scm._deleted_files()) 661 662 def test_delete_recursively(self): 663 self._shared_test_delete_recursively() 664 665 def test_delete_recursively_or_not(self): 666 self._shared_test_delete_recursively_or_not() 667 668 def test_move(self): 669 self._shared_test_move() 670 671 def test_move_recursive(self): 672 self._shared_test_move_recursive() 673 674 def test_exists(self): 675 self._shared_test_exists(self.scm, self.scm.commit_locally_with_message) 676 677 678class GitTestWithMock(SCMTestBase): 679 def make_scm(self): 680 scm = Git(cwd=".", executive=MockExecutive(), filesystem=MockFileSystem()) 681 scm.read_git_config = lambda *args, **kw: "MOCKKEY:MOCKVALUE" 682 return scm 683 684 def test_timestamp_of_revision(self): 685 scm = self.make_scm() 686 scm.find_checkout_root = lambda path: '' 687 scm._run_git = lambda args: 'Date: 2013-02-08 08:05:49 +0000' 688 self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-08T08:05:49Z') 689 690 scm._run_git = lambda args: 'Date: 2013-02-08 01:02:03 +0130' 691 self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-07T23:32:03Z') 692 693 scm._run_git = lambda args: 'Date: 2013-02-08 01:55:21 -0800' 694 self.assertEqual(scm.timestamp_of_revision('some-path', '12345'), '2013-02-08T09:55:21Z') 695