Upstream version 10.39.225.0
[platform/framework/web/crosswalk.git] / src / third_party / WebKit / Tools / Scripts / webkitpy / common / checkout / scm / scm_unittest.py
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
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')