Merge lp:~garyvdm/bzr/diff_using_gui into lp:bzr

Proposed by Gary van der Merwe
Status: Work in progress
Proposed branch: lp:~garyvdm/bzr/diff_using_gui
Merge into: lp:bzr
Diff against target: 274 lines (+139/-22)
4 files modified
NEWS (+9/-0)
bzrlib/builtins.py (+13/-1)
bzrlib/diff.py (+69/-19)
bzrlib/tests/test_diff.py (+48/-2)
To merge this branch: bzr merge lp:~garyvdm/bzr/diff_using_gui
Reviewer Review Type Date Requested Status
John A Meinel Needs Fixing
Gordon Tyler Needs Fixing
Review via email: mp+24076@code.launchpad.net

Description of the change

This makes "diff --using" work better with gui diff apps.

This allows one to either run the diff command once, with a list of files to diff, e.g.:
bzr diff --using "meld [--diff]"

or to run the command once per file, but run the processes simultaneously, e.g.:
bzr diff --using "WinMergeU.exe /s &"

This fixes Bug 490212.

To post a comment you must log in.
lp:~garyvdm/bzr/diff_using_gui updated
5172. By Gary van der Merwe

Update NEWS.

Revision history for this message
Gordon Tyler (doxxx) wrote :

Looks like there's a merge conflict in test_diff.

review: Needs Fixing
lp:~garyvdm/bzr/diff_using_gui updated
5173. By Gary van der Merwe

Merge bzr.dev.

Revision history for this message
Gary van der Merwe (garyvdm) wrote :

Thanks Gordon. Fixed. I did a uncommit, so you may need to do a pull --overwrite.

Revision history for this message
Aaron Bentley (abentley) wrote :

-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

On 04/25/2010 11:40 AM, Gary van der Merwe wrote:
> This allows one to either run the diff command once, with a list of files to diff, e.g.:
> bzr diff --using "meld [--diff]"
>
> or to run the command once per file, but run the processes simultaneously, e.g.:
> bzr diff --using "WinMergeU.exe /s &"

What about the other way of using meld, where you specify the
directories to diff and the command recursively diffs them? The
original difftools plugin supported "fldiff", "kdiff3", "kompare",
"meld", "opendiff" and "xxdiff" as "TreeDiffTools".

Aaron
-----BEGIN PGP SIGNATURE-----
Version: GnuPG v1.4.10 (GNU/Linux)
Comment: Using GnuPG with Mozilla - http://enigmail.mozdev.org/

iEYEARECAAYFAkvWUb4ACgkQ0F+nu1YWqI1AJgCfUhC2BYXThVDR7PF9bjRx+OE2
l6QAnRB1CBjF+1ebguEUJF2QcDxnBFgC
=bKRS
-----END PGP SIGNATURE-----

Revision history for this message
Gary van der Merwe (garyvdm) wrote :

On 27/04/2010 04:54, Aaron Bentley wrote:
> What about the other way of using meld, where you specify the
> directories to diff and the command recursively diffs them? The
> original difftools plugin supported "fldiff", "kdiff3", "kompare",
> "meld", "opendiff" and "xxdiff" as "TreeDiffTools".

I view that as a separate feature, which I've allready started working
on. I'm useing Jelmers new format option, which means to get that
behavior, you use:

  bzr diff --using meld --format ext-tree

Revision history for this message
John A Meinel (jameinel) wrote :

Having a mini-command line language to define what command to run seems a bit tricky, vs the original difftools method which just keeps a registry of known commands. I think Robert also agrees that communicating how to write a diff command is a bit hard to communicate. And telling people to just write a wrapper isn't seen as a reasonable solution on Windows.

As such, I'd rather see more of the difftools approach here, where you have a known list (registry) of tools that we know how to invoke on request.

248 + diff_obj = DiffFromTool(['python', '-c',
249 + 'print "@old_path @new_path"'],
250 + None, None, output,
251 + simultaneous_processes=True)

At a minimum, we should be using 'sys.executable' here, since you can't guaranteed that it is accessible simply as 'python'.

If you have simultaneous_process set, you should probably close proc.stdin. Otherwise you could be spawning multiple processes simultaneously that all want to read from stdin.

review: Needs Fixing
lp:~garyvdm/bzr/diff_using_gui updated
5174. By Gary van der Merwe

Merge bzr.dev.

5175. By Gary van der Merwe

Make tests pass (caused due to change in imports merged from bzr.dev.)

Unmerged revisions

5175. By Gary van der Merwe

Make tests pass (caused due to change in imports merged from bzr.dev.)

5174. By Gary van der Merwe

Merge bzr.dev.

5173. By Gary van der Merwe

Merge bzr.dev.

5172. By Gary van der Merwe

Update NEWS.

5171. By Gary van der Merwe

Test simultaneous_processes, and list_template behaviors.

5170. By Gary van der Merwe

Documentation for new --using usage.

5169. By Gary van der Merwe

Add tests for from_string.

5168. By Gary van der Merwe

Fix existing tests.

5167. By Gary van der Merwe

Add syntax for diff --using to launch with a list of files to diff.

5166. By Gary van der Merwe

Add syntax for diff --using to launch processes simultaneously.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'NEWS'
2--- NEWS 2010-06-15 07:44:29 +0000
3+++ NEWS 2010-06-16 09:03:28 +0000
4@@ -22,6 +22,15 @@
5 New Features
6 ************
7
8+* ``bzr diff --using`` now supports a options to work better with gui
9+ diff apps. This allows one to either run the diff command once, with
10+ a list of files to diff, e.g.:
11+ bzr diff --using "meld [--diff]"
12+ or to run the command once per file, but run the processes
13+ simultaneously, e.g.:
14+ bzr diff --using "WinMergeU.exe /s &"
15+ (Gary van der Merwe, #490212)
16+
17 * Support ``--directory`` option for a number of additional commands:
18 conflicts, merge-directive, missing, resolve, shelve, switch,
19 unshelve, whoami. (Martin von Gagern, #527878)
20
21=== modified file 'bzrlib/builtins.py'
22--- bzrlib/builtins.py 2010-06-07 02:38:04 +0000
23+++ bzrlib/builtins.py 2010-06-16 09:03:28 +0000
24@@ -1906,6 +1906,14 @@
25 Same as 'bzr diff' but prefix paths with old/ and new/::
26
27 bzr diff --prefix old/:new/
28+
29+ Launch meld, with each file in a tab::
30+
31+ bzr diff --using "meld [--diff]"
32+
33+ Launch meld, with each file in a window::
34+
35+ bzr diff --using "meld &"
36 """
37 _see_also = ['status']
38 takes_args = ['file*']
39@@ -1927,7 +1935,11 @@
40 'revision',
41 'change',
42 Option('using',
43- help='Use this command to compare files.',
44+ help='Use this command to compare files. This command will be run '
45+ 'for each file that differs. To launch the command for each '
46+ 'file simultaneously, suffex the command with "&". To launch '
47+ 'command once, with a list of files to diff, wrap the '
48+ 'arguments that need to be repeated in "[]". ',
49 type=unicode,
50 ),
51 RegistryOption('format',
52
53=== modified file 'bzrlib/diff.py'
54--- bzrlib/diff.py 2010-05-25 17:27:52 +0000
55+++ bzrlib/diff.py 2010-06-16 09:03:28 +0000
56@@ -722,21 +722,42 @@
57
58
59 class DiffFromTool(DiffPath):
60-
61 def __init__(self, command_template, old_tree, new_tree, to_file,
62- path_encoding='utf-8'):
63+ path_encoding='utf-8', simultaneous_processes=False,
64+ list_template=None):
65 DiffPath.__init__(self, old_tree, new_tree, to_file, path_encoding)
66 self.command_template = command_template
67 self._root = osutils.mkdtemp(prefix='bzr-diff-')
68+ self._procs = []
69+ self.simultaneous_processes = simultaneous_processes
70+ self.list_template = list_template
71+ if list_template:
72+ self._command_list = []
73
74 @classmethod
75 def from_string(klass, command_string, old_tree, new_tree, to_file,
76 path_encoding='utf-8'):
77- command_template = cmdline.split(command_string)
78+ list_template_match = re.match('(.*)\[(.*)\](.*)', command_string)
79+ if not list_template_match:
80+ command_template = cmdline.split(command_string)
81+ list_template = None
82+ else:
83+ command_template = (cmdline.split(list_template_match.group(1)) +
84+ cmdline.split(list_template_match.group(3)))
85+ list_template = cmdline.split(list_template_match.group(2))
86+
87+ if command_template[-1] == '&':
88+ simul = True
89+ command_template = command_template[:-1]
90+ else:
91+ simul = False
92+
93 if '@' not in command_string:
94- command_template.extend(['@old_path', '@new_path'])
95+ (list_template or command_template).extend(['@old_path',
96+ '@new_path'])
97 return klass(command_template, old_tree, new_tree, to_file,
98- path_encoding)
99+ path_encoding, simultaneous_processes=simul,
100+ list_template=list_template)
101
102 @classmethod
103 def make_from_diff_tree(klass, command_string):
104@@ -745,23 +766,32 @@
105 diff_tree.new_tree, diff_tree.to_file)
106 return from_diff_tree
107
108- def _get_command(self, old_path, new_path):
109+ def _get_command(self, old_path, new_path, template):
110 my_map = {'old_path': old_path, 'new_path': new_path}
111- return [AtTemplate(t).substitute(my_map) for t in
112- self.command_template]
113+ return [AtTemplate(t).substitute(my_map) for t in template]
114
115 def _execute(self, old_path, new_path):
116- command = self._get_command(old_path, new_path)
117- try:
118- proc = subprocess.Popen(command, stdout=subprocess.PIPE,
119- cwd=self._root)
120- except OSError, e:
121- if e.errno == errno.ENOENT:
122- raise errors.ExecutableMissing(command[0])
123+ if not self.list_template:
124+ command = self._get_command(old_path, new_path, self.command_template)
125+ try:
126+ proc = subprocess.Popen(command, stdout=subprocess.PIPE,
127+ cwd=self._root)
128+ except OSError, e:
129+ if e.errno == errno.ENOENT:
130+ raise errors.ExecutableMissing(command[0])
131+ else:
132+ raise
133+ if self.simultaneous_processes:
134+ self._procs.append(proc)
135+ return
136 else:
137- raise
138- self.to_file.write(proc.stdout.read())
139- return proc.wait()
140+ self.to_file.write(proc.stdout.read())
141+ return proc.wait()
142+ else:
143+ self._command_list.extend(
144+ self._get_command(old_path, new_path,
145+ self.list_template))
146+ return
147
148 def _try_symlink_root(self, tree, prefix):
149 if (getattr(tree, 'abspath', None) is None
150@@ -817,6 +847,24 @@
151 return old_disk_path, new_disk_path
152
153 def finish(self):
154+ if self.list_template and self._command_list:
155+ command = self.command_template + self._command_list
156+ try:
157+ proc = subprocess.Popen(command, stdout=subprocess.PIPE,
158+ cwd=self._root)
159+ except OSError, e:
160+ if e.errno == errno.ENOENT:
161+ raise errors.ExecutableMissing(command[0])
162+ else:
163+ raise
164+ self.to_file.write(proc.stdout.read())
165+ proc.wait()
166+
167+ if self.simultaneous_processes:
168+ for proc in self._procs:
169+ self.to_file.write(proc.stdout.read())
170+ proc.wait()
171+
172 try:
173 osutils.rmtree(self._root)
174 except OSError, e:
175@@ -831,6 +879,7 @@
176 file_id, old_path, new_path)
177 self._execute(old_disk_path, new_disk_path)
178
179+
180 def edit_file(self, file_id):
181 """Use this tool to edit a file.
182
183@@ -846,7 +895,8 @@
184 allow_write_new=True,
185 force_temp=True)[1]
186 command = self._get_command(osutils.pathjoin('old', old_path),
187- osutils.pathjoin('new', new_path))
188+ osutils.pathjoin('new', new_path),
189+ self.command_template)
190 subprocess.call(command, cwd=self._root)
191 new_file = open(new_abs_path, 'r')
192 try:
193
194=== modified file 'bzrlib/tests/test_diff.py'
195--- bzrlib/tests/test_diff.py 2010-05-20 08:08:20 +0000
196+++ bzrlib/tests/test_diff.py 2010-06-16 09:03:28 +0000
197@@ -1287,6 +1287,7 @@
198 self.addCleanup(diff_obj.finish)
199 self.assertEqual(['diff', '@old_path', '@new_path'],
200 diff_obj.command_template)
201+ self.assertEqual(False, diff_obj.simultaneous_processes)
202
203 def test_from_string_u5(self):
204 diff_obj = diff.DiffFromTool.from_string('diff "-u 5"',
205@@ -1295,7 +1296,25 @@
206 self.assertEqual(['diff', '-u 5', '@old_path', '@new_path'],
207 diff_obj.command_template)
208 self.assertEqual(['diff', '-u 5', 'old-path', 'new-path'],
209- diff_obj._get_command('old-path', 'new-path'))
210+ diff_obj._get_command('old-path', 'new-path',
211+ diff_obj.command_template))
212+
213+ def test_from_string_simultaneous_processes(self):
214+ diff_obj = diff.DiffFromTool.from_string('diff &', None, None, None)
215+ self.addCleanup(diff_obj.finish)
216+ self.assertEqual(['diff', '@old_path', '@new_path'],
217+ diff_obj.command_template)
218+ self.assertEqual(None, diff_obj.list_template)
219+ self.assertEqual(True, diff_obj.simultaneous_processes)
220+
221+ def test_from_string_list(self):
222+ diff_obj = diff.DiffFromTool.from_string('meld [--diff]', None, None, None)
223+ self.addCleanup(diff_obj.finish)
224+ self.assertEqual(['meld'],
225+ diff_obj.command_template)
226+ self.assertEqual(['--diff', '@old_path', '@new_path'],
227+ diff_obj.list_template)
228+ self.assertEqual(False, diff_obj.simultaneous_processes)
229
230 def test_from_string_path_with_backslashes(self):
231 self.requireFeature(features.backslashdir_feature)
232@@ -1305,7 +1324,8 @@
233 self.assertEqual(['C:\\Tools\\Diff.exe', '@old_path', '@new_path'],
234 diff_obj.command_template)
235 self.assertEqual(['C:\\Tools\\Diff.exe', 'old-path', 'new-path'],
236- diff_obj._get_command('old-path', 'new-path'))
237+ diff_obj._get_command('old-path', 'new-path',
238+ diff_obj.command_template))
239
240 def test_execute(self):
241 output = StringIO()
242@@ -1316,6 +1336,32 @@
243 diff_obj._execute('old', 'new')
244 self.assertEqual(output.getvalue().rstrip(), 'old new')
245
246+ def test_execute_simultaneous_processes(self):
247+ output = StringIO()
248+ diff_obj = diff.DiffFromTool(['python', '-c',
249+ 'print "@old_path @new_path"'],
250+ None, None, output,
251+ simultaneous_processes=True)
252+ diff_obj._execute('old1', 'new1')
253+ diff_obj._execute('old2', 'new2')
254+ self.assertEqual(output.getvalue().rstrip(), '')
255+ diff_obj.finish()
256+ self.assertEqual(output.getvalue().rstrip(), 'old1 new1\n'
257+ 'old2 new2')
258+
259+ def test_execute_list(self):
260+ output = StringIO()
261+ diff_obj = diff.DiffFromTool(['python', '-c', 'import sys; '
262+ 'print sys.argv[1:]'],
263+ None, None, output,
264+ list_template=['@old_path', '@new_path'])
265+ diff_obj._execute('old1', 'new1')
266+ diff_obj._execute('old2', 'new2')
267+ self.assertEqual(output.getvalue().rstrip(), '')
268+ diff_obj.finish()
269+ self.assertEqual(output.getvalue().rstrip(), "['old1', 'new1', "
270+ "'old2', 'new2']")
271+
272 def test_excute_missing(self):
273 diff_obj = diff.DiffFromTool(['a-tool-which-is-unlikely-to-exist'],
274 None, None, None)