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 34import unittest 35 36from webkitpy.common.system.executive import Executive, ScriptError 37from webkitpy.common.system.executive_mock import MockExecutive 38from webkitpy.common.system.filesystem import FileSystem 39from webkitpy.common.system.filesystem_mock import MockFileSystem 40from webkitpy.common.checkout.scm.detection import detect_scm_system 41from webkitpy.common.checkout.scm.git import Git, AmbiguousCommitError 42from webkitpy.common.checkout.scm.scm import SCM 43from 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). 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 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 259class 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 308class 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 367class 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 689class 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