1# Software License Agreement (BSD License)
2#
3# Copyright (c) 2009, Willow Garage, Inc.
4# All rights reserved.
5#
6# Redistribution and use in source and binary forms, with or without
7# modification, are permitted provided that the following conditions
8# are met:
9#
10#  * Redistributions of source code must retain the above copyright
11#    notice, this list of conditions and the following disclaimer.
12#  * Redistributions in binary form must reproduce the above
13#    copyright notice, this list of conditions and the following
14#    disclaimer in the documentation and/or other materials provided
15#    with the distribution.
16#  * Neither the name of Willow Garage, Inc. nor the names of its
17#    contributors may be used to endorse or promote products derived
18#    from this software without specific prior written permission.
19#
20# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
21# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
22# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
23# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
24# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
25# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
26# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
27# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
28# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
29# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
30# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
31# POSSIBILITY OF SUCH DAMAGE.
32
33import os
34import sys
35import subprocess
36
37from test.io_wrapper import StringIO
38
39import wstool
40import wstool.helpers
41import wstool.wstool_cli
42from wstool.wstool_cli import WstoolCLI
43from wstool.wstool_cli import wstool_main
44
45import test.scm_test_base
46from test.scm_test_base import AbstractSCMTest, _add_to_file, _nth_line_split
47
48
49def create_git_repo(remote_path):
50    # create a "remote" repo
51    subprocess.check_call(["git", "init"], cwd=remote_path)
52    subprocess.check_call(["touch", "fixed.txt"], cwd=remote_path)
53    subprocess.check_call(["touch", "modified.txt"], cwd=remote_path)
54    subprocess.check_call(["touch", "modified-fs.txt"], cwd=remote_path)
55    subprocess.check_call(["touch", "deleted.txt"], cwd=remote_path)
56    subprocess.check_call(["touch", "deleted-fs.txt"], cwd=remote_path)
57    subprocess.check_call(["git", "add", "*"], cwd=remote_path)
58    subprocess.check_call(["git", "commit", "-m", "modified"], cwd=remote_path)
59
60
61def modify_git_repo(clone_path):
62    # make local modifications
63    subprocess.check_call(["rm", "deleted-fs.txt"], cwd=clone_path)
64    subprocess.check_call(["git", "rm", "deleted.txt"], cwd=clone_path)
65    _add_to_file(os.path.join(clone_path, "modified-fs.txt"), "foo\n")
66    _add_to_file(os.path.join(clone_path, "modified.txt"), "foo\n")
67    subprocess.check_call(["git", "add", "modified.txt"], cwd=clone_path)
68    _add_to_file(os.path.join(clone_path, "added-fs.txt"), "tada\n")
69    _add_to_file(os.path.join(clone_path, "added.txt"), "flam\n")
70    subprocess.check_call(["git", "add", "added.txt"], cwd=clone_path)
71
72
73class WstoolDiffGitTest(AbstractSCMTest):
74
75    @classmethod
76    def setUpClass(self):
77        AbstractSCMTest.setUpClass()
78        remote_path = os.path.join(self.test_root_path, "remote")
79        os.makedirs(remote_path)
80
81        create_git_repo(remote_path)
82
83        # wstool the remote repo and fake ros
84        _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone, uri: ../remote}")
85
86        cmd = ["wstool", "update", "-t", "ws"]
87        os.chdir(self.test_root_path)
88        wstool_main(cmd)
89
90        clone_path = os.path.join(self.local_path, "clone")
91
92        modify_git_repo(clone_path)
93
94    def check_diff_output(self, output):
95        # sha ids are always same with git
96        self.assertEqual('diff --git clone/added.txt clone/added.txt\nnew file mode 100644\nindex 0000000..8d63207\n--- /dev/null\n+++ clone/added.txt\n@@ -0,0 +1 @@\n+flam\ndiff --git clone/deleted-fs.txt clone/deleted-fs.txt\ndeleted file mode 100644\nindex e69de29..0000000\ndiff --git clone/deleted.txt clone/deleted.txt\ndeleted file mode 100644\nindex e69de29..0000000\ndiff --git clone/modified-fs.txt clone/modified-fs.txt\nindex e69de29..257cc56 100644\n--- clone/modified-fs.txt\n+++ clone/modified-fs.txt\n@@ -0,0 +1 @@\n+foo\ndiff --git clone/modified.txt clone/modified.txt\nindex e69de29..257cc56 100644\n--- clone/modified.txt\n+++ clone/modified.txt\n@@ -0,0 +1 @@\n+foo', output.rstrip())
97
98    def test_wstool_diff_git_outside(self):
99        """Test diff output for git when run outside workspace"""
100
101        cmd = ["wstool", "diff", "-t", "ws"]
102        os.chdir(self.test_root_path)
103        sys.stdout = output = StringIO()
104        wstool_main(cmd)
105        sys.stdout = sys.__stdout__
106        output = output.getvalue()
107        self.check_diff_output(output)
108
109        cli = WstoolCLI()
110        self.assertEqual(0, cli.cmd_diff(os.path.join(self.test_root_path, 'ws'), []))
111
112    def test_wstool_diff_git_inside(self):
113        """Test diff output for git when run inside workspace"""
114        directory = self.test_root_path + "/ws"
115        cmd = ["wstool", "diff"]
116        os.chdir(directory)
117        sys.stdout = output = StringIO()
118        wstool_main(cmd)
119        output = output.getvalue()
120        sys.stdout = sys.__stdout__
121        self.check_diff_output(output)
122
123        cli = WstoolCLI()
124        self.assertEqual(0, cli.cmd_diff(directory, []))
125
126    def test_wstool_status_git_inside(self):
127        """Test status output for git when run inside workspace"""
128        directory = self.test_root_path + "/ws"
129
130        cmd = ["wstool", "status"]
131        os.chdir(directory)
132        sys.stdout = output = StringIO()
133        wstool_main(cmd)
134        output = output.getvalue()
135        sys.stdout = sys.__stdout__
136        self.assertEqual('A       clone/added.txt\n D      clone/deleted-fs.txt\nD       clone/deleted.txt\n M      clone/modified-fs.txt\nM       clone/modified.txt\n', output)
137
138        cli = WstoolCLI()
139        self.assertEqual(0, cli.cmd_diff(directory, []))
140
141    def test_Wstool_status_git_outside(self):
142        """Test status output for git when run outside workspace"""
143
144        cmd = ["wstool", "status", "-t", "ws"]
145        os.chdir(self.test_root_path)
146        sys.stdout = output = StringIO()
147        wstool_main(cmd)
148        sys.stdout = sys.__stdout__
149        output = output.getvalue()
150        self.assertEqual('A       clone/added.txt\n D      clone/deleted-fs.txt\nD       clone/deleted.txt\n M      clone/modified-fs.txt\nM       clone/modified.txt\n', output)
151
152        cli = WstoolCLI()
153        self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), []))
154
155    def test_Wstool_status_git_untracked(self):
156        """Test untracked status output for git when run outside workspace"""
157
158        cmd = ["wstool", "status", "-t", "ws", "--untracked"]
159        os.chdir(self.test_root_path)
160        sys.stdout = output = StringIO()
161        wstool_main(cmd)
162        sys.stdout = sys.__stdout__
163        output = output.getvalue()
164        self.assertEqual('A       clone/added.txt\n D      clone/deleted-fs.txt\nD       clone/deleted.txt\n M      clone/modified-fs.txt\nM       clone/modified.txt\n??      clone/added-fs.txt\n', output)
165
166        cli = WstoolCLI()
167        self.assertEqual(0, cli.cmd_status(os.path.join(self.test_root_path, 'ws'), ["--untracked"]))
168
169    def test_wstool_info_git(self):
170        cmd = ["wstool", "info", "-t", "ws"]
171        os.chdir(self.test_root_path)
172        sys.stdout = output = StringIO()
173        wstool_main(cmd)
174        output = output.getvalue()
175        tokens = _nth_line_split(-2, output)
176        self.assertEqual(['clone', 'M', 'git'], tokens[0:3])
177        tokens2 = _nth_line_split(-1, output)
178        self.assertEqual(1, len(tokens2))
179        self.assertEqual('../ros', tokens2[0])
180
181        cli = WstoolCLI()
182        self.assertEqual(0, cli.cmd_info(os.path.join(self.test_root_path, 'ws'), []))
183
184
185class WstoolInfoGitTest(AbstractSCMTest):
186
187    def setUp(self):
188        AbstractSCMTest.setUp(self)
189        self.remote_path = os.path.join(self.test_root_path, "remote")
190        os.makedirs(self.remote_path)
191
192        # create a "remote" repo
193        subprocess.check_call(["git", "init"], cwd=self.remote_path)
194        subprocess.check_call(["touch", "test.txt"], cwd=self.remote_path)
195        subprocess.check_call(["git", "add", "*"], cwd=self.remote_path)
196        subprocess.check_call(["git", "commit", "-m", "modified"], cwd=self.remote_path)
197        po = subprocess.Popen(["git", "log", "-n", "1", "--pretty=format:\"%H\""], cwd=self.remote_path, stdout=subprocess.PIPE)
198        self.version_init = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')[0:12]
199        subprocess.check_call(["git", "tag", "footag"], cwd=self.remote_path)
200        subprocess.check_call(["touch", "test2.txt"], cwd=self.remote_path)
201        subprocess.check_call(["git", "add", "*"], cwd=self.remote_path)
202        subprocess.check_call(["git", "commit", "-m", "modified"], cwd=self.remote_path)
203        po = subprocess.Popen(["git", "log", "-n", "1", "--pretty=format:\"%H\""], cwd=self.remote_path, stdout=subprocess.PIPE)
204        self.version_end = po.stdout.read().decode('UTF-8').rstrip('"').lstrip('"')[0:12]
205
206        # wstool the remote repo and fake ros
207        _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone, uri: ../remote}")
208        self.clone_path = os.path.join(self.local_path, "clone")
209
210        cmd = ["wstool", "update"]
211        os.chdir(self.local_path)
212        sys.stdout = output = StringIO()
213        wstool_main(cmd)
214        output = output.getvalue()
215        sys.stdout = sys.__stdout__
216
217    def test_wstool_detailed_localpath_info(self):
218        cmd = ["wstool", "info", "-t", "ws", "--managed-only"]
219        os.chdir(self.test_root_path)
220        sys.stdout = output = StringIO()
221        wstool_main(cmd)
222        output = output.getvalue()
223        tokens = _nth_line_split(-2, output)
224        self.assertEqual(['clone', 'git', 'master', '(-)', self.version_end, self.remote_path], tokens)
225
226        # test when remote version is different
227        subprocess.check_call(["git", "reset", "--hard", "HEAD~1"], cwd=self.clone_path)
228        sys.stdout = output = StringIO()
229        wstool_main(cmd)
230        output = output.getvalue()
231        tokens = _nth_line_split(-2, output)
232        self.assertEqual(['clone', 'C', 'git', 'master', '(-)', self.version_init, self.remote_path], tokens)
233        # return branch back to original revision
234        subprocess.check_call(["git", "reset", "--hard", self.version_end], cwd=self.clone_path)
235
236        # make local modifications check
237        subprocess.check_call(["rm", "test2.txt"], cwd=self.clone_path)
238        sys.stdout = output = StringIO()
239        wstool_main(cmd)
240        output = output.getvalue()
241        tokens = _nth_line_split(-2, output)
242        self.assertEqual(['clone', 'M', 'git', 'master', '(-)', self.version_end, self.remote_path], tokens)
243
244        subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path)
245        _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone, uri: ../remote, version: \"footag\"}")
246        # test when version is different
247        sys.stdout = output = StringIO()
248        wstool_main(cmd)
249        output = output.getvalue()
250        tokens = _nth_line_split(-2, output)
251        self.assertEqual(['clone', 'MV', 'git', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens)
252        # test when tracking branch is different from current branch
253        subprocess.check_call(["git", "checkout", "-b", "test_branch"], cwd=self.clone_path)
254        subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.remote", "origin"], cwd=self.clone_path)
255        subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.merge", "master"], cwd=self.clone_path)
256        sys.stdout = output = StringIO()
257        wstool_main(cmd)
258        output = output.getvalue()
259        tokens = _nth_line_split(-2, output)
260        self.assertEqual(['clone', 'MV', 'git', 'test_branch', '<', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens)
261
262        # test when remote is different from origin by rename
263        subprocess.check_call(["git", "remote", "rename", "origin", "remote2"], cwd=self.clone_path)
264        subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.remote", "remote2"], cwd=self.clone_path)
265        sys.stdout = output = StringIO()
266        wstool_main(cmd)
267        output = output.getvalue()
268        tokens = _nth_line_split(-2, output)
269        self.assertEqual(['clone', 'MV', 'git', 'test_branch', '<', 'remote2/master', '(footag)', self.version_end, "(%s)" % self.version_init, "(%s)" % self.remote_path], tokens)
270        # return remote name to origin
271        subprocess.check_call(["git", "remote", "rename", "remote2", "origin"], cwd=self.clone_path)
272
273        # test when remote is different from origin, no fetch
274        subprocess.check_call(["git", "remote", "add", "remote2", "../../remote"], cwd=self.clone_path)
275        subprocess.check_call(["git", "config", "--replace-all", "branch.test_branch.remote", "remote2"], cwd=self.clone_path)
276        sys.stdout = output = StringIO()
277        wstool_main(cmd)
278        output = output.getvalue()
279        tokens = _nth_line_split(-2, output)
280        self.assertEqual(['clone', 'MV', 'git', 'test_branch', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens)
281
282
283        # test when remote is different from origin, with fetch
284        sys.stdout = output = StringIO()
285        wstool_main(cmd + ['--fetch'])
286        output = output.getvalue()
287        tokens = _nth_line_split(-2, output)
288        self.assertEqual(['clone', 'MV', 'git', 'test_branch', '<', 'remote2/master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens)
289
290
291        # return branch back to master
292        subprocess.check_call(["git", "checkout", "master"], cwd=self.clone_path)
293
294        # using a denormalized local-name here
295        subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path)
296        _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: clone/../clone, uri: ../remote, version: \"footag\"}")
297        sys.stdout = output = StringIO()
298        wstool_main(cmd)
299        output = output.getvalue()
300        tokens = _nth_line_split(-2, output)
301        self.assertEqual(['clone', 'MV', 'git', 'master', '(footag)', self.version_end, "(%s)" %
302                         self.version_init, self.remote_path], tokens)
303
304        # using an absolute path to clone dir here
305        subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path)
306        _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: '"+self.clone_path+"', uri: ../remote, version: \"footag\"}")
307        sys.stdout = output = StringIO()
308        wstool_main(cmd)
309        output = output.getvalue()
310        tokens = _nth_line_split(-2, output)
311        self.assertEqual([self.clone_path, 'MV', 'git', 'master', '(footag)', self.version_end, "(%s)" % self.version_init, self.remote_path], tokens)
312
313        # using an absolute path here where relative path is shorter to display (also checks x for missing)
314        subprocess.check_call(["rm", ".rosinstall"], cwd=self.local_path)
315        _add_to_file(os.path.join(self.local_path, ".rosinstall"), "- other: {local-name: ../ros}\n- git: {local-name: '"+os.path.join(self.local_path, "../foo")+"', uri: ../remote, version: \"footag\"}")
316        sys.stdout = output = StringIO()
317        wstool_main(cmd)
318        output = output.getvalue()
319        tokens = _nth_line_split(-2, output)
320        localname = os.path.join(os.path.dirname(self.local_path), 'foo')
321        self.assertEqual([localname, 'x', 'git', '(footag)', self.remote_path], tokens)
322