Merge lp:~doxxx/bzr/mergetools-commands into lp:bzr

Proposed by Gordon Tyler
Status: Work in progress
Proposed branch: lp:~doxxx/bzr/mergetools-commands
Merge into: lp:bzr
Prerequisite: lp:~doxxx/bzr/mergetools
Diff against target: 291 lines (+128/-19)
6 files modified
bzrlib/builtins.py (+41/-13)
bzrlib/conflicts.py (+45/-1)
bzrlib/mergetools.py (+18/-5)
bzrlib/tests/test_mergetools.py (+15/-0)
doc/en/release-notes/bzr-2.4.txt (+4/-0)
doc/en/whats-new/whats-new-in-2.4.txt (+5/-0)
To merge this branch: bzr merge lp:~doxxx/bzr/mergetools-commands
Reviewer Review Type Date Requested Status
Vincent Ladeuil Needs Fixing
Review via email: mp+40924@code.launchpad.net

Commit message

(doxxx) Added --resolve-using=ARG option to merge and remerge commands to resolve conflicts using an external merge tool.

Description of the change

Building on the new bzrlib.mergetools module added by lp:~doxxx/bzr/mergetools, this merge proposal adds a --resolve-using=ARG option to the ``bzr merge`` and ``bzr remerge`` commands to invoke the specified merge tool for each text conflict.

To post a comment you must log in.
Revision history for this message
Vincent Ladeuil (vila) wrote :

This is far clearer.

Making --detect or --list mandatory sounds weird though. --list should be default but doesn't it duplicate 'bzr config bzr.mergetools' ?

Or rather 'bzr config *mergetool*' to also get default_mergetool.

And then... why not use bzr.mergetools.default and forbid 'default' as a valid merge tool name in your other proposal ?

It would be nice to mention that --detect *also* set the mergetools in the the config (which one ?).

Which one is the default in that case ? None ?

If that's the case, this means the user must specify which merge tool he want to use no ?

Since you're providing access to pre-defined merge tools as soon as you have detected them, why not go a step further and make this '--detect' step optional and provides the default command-line for, say kdiff3, if the user try to use --resolve-using kdiff3 ?

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

On 11/25/2010 5:19 AM, Vincent Ladeuil wrote:
> Making --detect or --list mandatory sounds weird though. --list
> should be default but doesn't it duplicate 'bzr config
> bzr.mergetools' ?
> Or rather 'bzr config *mergetool*' to also get default_mergetool.

Making --list the default operation sounds like a good idea. Yes, this
is duplicating functionality from `bzr config`. Would you rather have a
single command called 'detect-merge-tools'? I think it's nice to have a
pretty-printed list of merge tools.

> And then... why not use bzr.mergetools.default and forbid 'default'
> as a valid merge tool name in your other proposal ?

I suppose I could. :)

> It would be nice to mention that --detect *also* set the mergetools
> in the the config (which one ?).
>
> Which one is the default in that case ? None ?

None, it doesn't set a default since the command-line doesn't need a
default, you always have to specify a name when you want to use a merge
tool with merge or remerge. The default only applies to qbzr and there
you have qconfig to set the default.

> If that's the case, this means the user must specify which merge tool
> he want to use no ?

Correct.

> Since you're providing access to pre-defined merge tools as soon as
> you have detected them, why not go a step further and make this
> '--detect' step optional and provides the default command-line for,
> say kdiff3, if the user try to use --resolve-using kdiff3 ?

Yes, I could try that. With an appropriate message to the effect of
"auto-detecting kdiff3 on PATH"? Or is that unnecessary?

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> Gordon Tyler <email address hidden> writes:

    > On 11/25/2010 5:19 AM, Vincent Ladeuil wrote:
    >> Making --detect or --list mandatory sounds weird though. --list
    >> should be default but doesn't it duplicate 'bzr config
    >> bzr.mergetools' ?
    >> Or rather 'bzr config *mergetool*' to also get default_mergetool.

    > Making --list the default operation sounds like a good idea. Yes, this
    > is duplicating functionality from `bzr config`. Would you rather have a
    > single command called 'detect-merge-tools'?

May be, if we still need it.

Keep in mind that the results it produces will stick. So the resulting
config will be wrong in the following cases:

- when a new merge tool is installed by the user,
- when an existing merged tool is updated or uninstalled by the user,
- when a new bzr version provide new defaults for a given merge tool.

    > I think it's nice to have a pretty-printed list of merge tools.

But apart from shortening the merge tool option name, what is pretty
here ?

Morevoer it doesn't display *where* the command line is defined if the
user put them in locations.conf or branch.conf (again, getting away from
a core implementation means more work ;).

I think the features are good to have internally, but I'm not sure we
need to expose them at the command line level.

    >> And then... why not use bzr.mergetools.default and forbid 'default'
    >> as a valid merge tool name in your other proposal ?

    > I suppose I could. :)

    >> It would be nice to mention that --detect *also* set the mergetools
    >> in the the config (which one ?).
    >>
    >> Which one is the default in that case ? None ?

    > None, it doesn't set a default since the command-line doesn't need a
    > default, you always have to specify a name when you want to use a merge
    > tool with merge or remerge.

Too bad no ?

    > The default only applies to qbzr and there you have qconfig to set
    > the default.

plugins should be able to override the defaults provided by bzr, but if
a user speficy it in some ways, it would be nice to respect that too
(core implementation again ;).

    >> If that's the case, this means the user must specify which merge tool
    >> he want to use no ?

    > Correct.

    >> Since you're providing access to pre-defined merge tools as soon as
    >> you have detected them, why not go a step further and make this
    >> '--detect' step optional and provides the default command-line for,
    >> say kdiff3, if the user try to use --resolve-using kdiff3 ?

    > Yes, I could try that. With an appropriate message to the effect of
    > "auto-detecting kdiff3 on PATH"? Or is that unnecessary?

I don't think it's necessary, a mutter() (i.e. in .bzr.log) may be enough.

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

On Thu, November 25, 2010 10:31 am, Vincent Ladeuil wrote:
> > Making --list the default operation sounds like a good idea. Yes,
> this
> > is duplicating functionality from `bzr config`. Would you rather
> have a
> > single command called 'detect-merge-tools'?
>
> May be, if we still need it.
>
> Keep in mind that the results it produces will stick. So the resulting
> config will be wrong in the following cases:
>
> - when a new merge tool is installed by the user,
> - when an existing merged tool is updated or uninstalled by the user,
> - when a new bzr version provide new defaults for a given merge tool.

How will it be wrong? The detection process doesn't overwrite existing
merge tools. It only adds merge tools that it detects for which there
isn't already a same-named merge tool in the config.

> > I think it's nice to have a pretty-printed list of merge tools.
>
> But apart from shortening the merge tool option name, what is pretty
> here ?
>
> Morevoer it doesn't display *where* the command line is defined if the
> user put them in locations.conf or branch.conf (again, getting away from
> a core implementation means more work ;).
>
> I think the features are good to have internally, but I'm not sure we
> need to expose them at the command line level.

However, removing the mergetools command entirely and trying to
auto-detect when they use --resolve-using=kdiff3 leaves me wondering how
are users going to know that bzr already can use kdiff3/meld/etc.? I
suppose we could list the known merge tools in the merge and remerge help,
or point them to the configuration help topic where we also list the known
merge tools and the commandlines that we use for them.

This also may look a little odd/ugly in the GUI since it will have to list
all the predefined known merge tools that can be found on the PATH in
addition to the user-defined merge tools and they may have defined their
own kdiff3/meld/etc. merge tool.

I just think it's nice to have a bzr command that helps the user define
the merge tools instead of just throwing them in the deep end (aka config
file).

> > None, it doesn't set a default since the command-line doesn't need a
> > default, you always have to specify a name when you want to use a
> merge
> > tool with merge or remerge.
>
> Too bad no ?

It's very awkward, if not impossible, to have a --resolve-using option
which has an optional value to indicate the tool to use. I suppose there
could be a --resolve-using-default option or treat --resolve-using=default
as referring to the default merge tool and not a merge tool called
'default'.

> > The default only applies to qbzr and there you have qconfig to set
> > the default.
>
> plugins should be able to override the defaults provided by bzr, but if
> a user speficy it in some ways, it would be nice to respect that too
> (core implementation again ;).

I don't understand...

Revision history for this message
Vincent Ladeuil (vila) wrote :
Download full text (3.6 KiB)

> I think I've addressed all of the comments so far. Please have another look
> and let me know if I've missed anything.

Well, for a start you missed the ones I proposed earlier in
lp:~vila/bzr/mergetools :-D

The missing vertical spaces, the config options renaming and the
use of overrideAttr and they can't be re-merged now.

I think poolie mentioned early in these reviews that we try to
avoid the "smart" operators (__eq__, __ne__), see
doc/developers/code-style.txt 'Imitating standard objects'. The
rationale is it's often more work to maintain these methods than
having explicit comparison operators (they make the code clearer
and easier to maintain).

The arg splitting functions are not necessary, the MergeTool
object receives the commandline when built. It should just
preserve it as-is, no need to rebuild it (splitting and
un-spliting just add possible failure points). This would also
get rid of the three commandline accessors.

There is also a failing test:
ERROR: bzrlib.tests.test_osutils.TestFindExecutableInPath.test_other
----------------------------------------------------------------------
_StringException: Text attachment: log
------------

------------
Text attachment: traceback
------------
Traceback (most recent call last):
  File "/home/vila/lib/python/testtools/runtest.py", line 144, in _run_user
    return fn(*args)
  File "/home/vila/lib/python/testtools/testcase.py", line 487, in _run_test_method
    testMethod()
  File "/home/vila/src/bzr/reviews/mergetools/bzrlib/tests/test_osutils.py", line 2133, in test_other
    self.assertTrue(osutils.is_executable_on_path('sh') is not None)
AttributeError: 'module' object has no attribute 'is_executable_on_path'
------------

But this goes in the right direction nevertheless :)

So, I went ahead fixing the style issues, renaming the options,
making the command line splitting internal and deleting the
associated accessors (and tests). This means making the merge
tool 'name' mandatory which also meant making _KNOWN_MERGE_TOOLS
a dict, until it becomes a proper registry (you may want to fix
the module docstring until it really is a registry).

There are still some weird things around find_executable_on_path,
PATHEXT is windows specific but seems to be applied
unconditionnally. Also, I think windows search in the current
directory if PATH is empty (which we may not want to mimick but
is worth documenting too).

In is_available, you check only for the existence of the file and
not whether the x bit is is set. There is a grey area there that
deserves at least a FIXME comment or better, a fix with proper
multi-platform tests (I won't block on that as long as it's
documented (i.e. a FIXME will do)).

I still don't agree with set_merge_tools, it will hardwire
_KNOWN_MERGE_TOOLS in whatever config file it is used against,
defeating the purpose of having default values configured (and
kept up to date) by bzr itself.

I think the core feature here is to check the availability of a
merge tool, but this needs to be done when bzr is about to need
it, doing it ahead of time is wrong as things could change. A
tool that you registered may have disapeared and a tool that you
ignored may have appeared...

Read more...

review: Needs Fixing
Revision history for this message
Gordon Tyler (doxxx) wrote :
Download full text (4.3 KiB)

On 12/6/2010 10:31 AM, Vincent Ladeuil wrote:
> The missing vertical spaces, the config options renaming and the
> use of overrideAttr and they can't be re-merged now.

Woops, I thought the 'bzr.' prefix was some weird notation that you were
just using for the purposes of discussion. :)

And somehow I missed what you said about overrideAttr. That's cool...

> I think poolie mentioned early in these reviews that we try to
> avoid the "smart" operators (__eq__, __ne__), see
> doc/developers/code-style.txt 'Imitating standard objects'. The
> rationale is it's often more work to maintain these methods than
> having explicit comparison operators (they make the code clearer
> and easier to maintain).

I must have missed that. I jsut had a look through the code and I don't
think I'm using them anymore. I needed them for sorting MergeTools in
lexical order for comparing lists during tests, I think. But the tests
don't do that anymore.

> The arg splitting functions are not necessary, the MergeTool
> object receives the commandline when built. It should just
> preserve it as-is, no need to rebuild it (splitting and
> un-spliting just add possible failure points). This would also
> get rid of the three commandline accessors.

Since I'm using string.format to do filename substitution, you're right
that this can be done with a single string commandline form.

> There is also a failing test:
> ERROR: bzrlib.tests.test_osutils.TestFindExecutableInPath.test_other
> File "/home/vila/src/bzr/reviews/mergetools/bzrlib/tests/test_osutils.py", line 2133, in test_other
> self.assertTrue(osutils.is_executable_on_path('sh') is not None)
> AttributeError: 'module' object has no attribute 'is_executable_on_path'

My bad, missed a reference when I renamed the function.

> So, I went ahead fixing the style issues, renaming the options,
> making the command line splitting internal and deleting the
> associated accessors (and tests). This means making the merge
> tool 'name' mandatory which also meant making _KNOWN_MERGE_TOOLS
> a dict, until it becomes a proper registry (you may want to fix
> the module docstring until it really is a registry).

Ok.

> There are still some weird things around find_executable_on_path,
> PATHEXT is windows specific but seems to be applied
> unconditionnally. Also, I think windows search in the current
> directory if PATH is empty (which we may not want to mimick but
> is worth documenting too).

PATHEXT is applied if it exists. Since it won't exist on non-win32, that
shouldn't be a problem, but for safety sake I suppose we could add a
platform check.

> In is_available, you check only for the existence of the file and
> not whether the x bit is is set. There is a grey area there that
> deserves at least a FIXME comment or better, a fix with proper
> multi-platform tests (I won't block on that as long as it's
> documented (i.e. a FIXME will do)).

Right. I should use the same os.access call that find_executable_on_path
uses.

> I still don't agree with set_merge_tools, it will hardwire
> _KNOWN_MERGE_TOOLS in whatever config file it is used against,
> defeating the purpose of having default values configured (and
> kept up to date)...

Read more...

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

On 12/6/2010 7:22 PM, Gordon Tyler wrote:
> Since I'm using string.format to do filename substitution, you're right
> that this can be done with a single string commandline form.

Although, the way you're doing it in your branch is fine too. So I don't
think I'll change that.

>> In is_available, you check only for the existence of the file and
>> not whether the x bit is is set. There is a grey area there that
>> deserves at least a FIXME comment or better, a fix with proper
>> multi-platform tests (I won't block on that as long as it's
>> documented (i.e. a FIXME will do)).
>
> Right. I should use the same os.access call that find_executable_on_path
> uses.

This actually turned out to be more complicated than I thought since
os.access(path, os.X_OK) returns True for all extant files on win32. But
I believe I have it solved now.

Revision history for this message
Vincent Ladeuil (vila) wrote :

I'm a bit lost here... in which mp are you handling this is_available related code ?

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

On 12/7/2010 6:44 AM, Vincent Ladeuil wrote:
> I'm a bit lost here... in which mp are you handling this is_available related code ?

This mp is purely for code changes directly related to modification of
bzr builtin commands like merge and remerge. Anything to do with base
mergetools module functionality like is_available is done in the other
mp, https://code.launchpad.net/~doxxx/bzr/mergetools/+merge/40923.

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

I think this is ready for review, now that the prereq has been approved.

lp:~doxxx/bzr/mergetools-commands updated
5420. By Gordon Tyler

Merged mergetools into mergetools-commands.

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

Could I get some eyeballs on this? It makes the facilities added by pre-req branch actually usable on the command-line.

Revision history for this message
Vincent Ladeuil (vila) wrote :

Argh, it looks like my review get lost :-(

Let's try again...

54 + mergetools.resolve_conflicts(tree.conflicts(), cmdline)
55 + return retval

mergetools.resolve_conflicts should update 'retval' no ?

86 + if resolve_using is not None:
87 + conf = _mod_config.GlobalConfig()
88 + cmdline = conf.find_merge_tool(resolve_using)
89 + if cmdline is None:
90 + raise errors.BzrCommandError(
91 + 'Unrecognized merge tool: %s' % resolve_using)
92 + if not mergetools.check_availability(cmdline):
93 + raise errors.BzrCommandError(
94 + 'External merge tool is not available: %s' %
95 + resolve_using)
96 + mergetools.resolve_conflicts(tree.conflicts(), cmdline)

Same remark, plus this code is screaming: 'put me in a helper !', you have almost the same block above. Or may be that block should just be embedded in resolve_conflicts ?

132 + action = 'tool'

Be bold ! s/tool/mergetools/ :)

141 + conf = config.GlobalConfig()
142 + cmdline = conf.find_merge_tool(using)
143 + if cmdline is None:
144 + raise errors.BzrCommandError(
145 + 'Unrecognized merge tool: %s' % using)
146 + if not mergetools.check_availability(cmdline):
147 + raise errors.BzrCommandError(
148 + 'Merge tool is not available: %s' % using)

Or may be it's really a distinct helper :) Whatever you chose, make sure a wt object is available, you use only GlobalConfig so far, but in the long term this would probably be a branch or wt config, both of which could be accessed via a wt.

161 + if verbose:
162 + ui.ui_factory.show_message('Invoking %s on %s...' %
163 + (merge_tool.get_name(), file))

Hmm, until we re-think the way we resolve conflicts I'd prefer that the feedback is the same for all the different ways to resolve a conflict. This particular message looks more like a trace.mutter() to me.

And since this is the only use of the 'verbose' option, I think we can just remove it.

168 + ui.ui_factory.show_message('%d conflict(s) resolved.' % resolved)
169 + unresolved = tree.conflicts()
170 + if len(unresolved) > 0:
171 + ui.ui_factory.show_message('Remaining conflicts:')
172 + for conflict in unresolved:
173 + ui.ui_factory.show_message(str(conflict))

This seems to depart from the way the conflicts are reported in the other cases for no good reason, can you unify it instead ?

More tests seem to be needed here, for testing the return codes of mergetools.resolve_conflicts with some simplified tool that can simulate resolving a conflict or not.

An then some more blackbox tests to make sure its integration in resolve is correct too.

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

Thanks for the review. I'll go over it soon.

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

On 2/9/2011 9:37 AM, Vincent Ladeuil wrote:
> mergetools.resolve_conflicts should update 'retval' no ?

Yup.

> 86 + if resolve_using is not None:
> 87 + conf = _mod_config.GlobalConfig()
> 88 + cmdline = conf.find_merge_tool(resolve_using)
> 89 + if cmdline is None:
> 90 + raise errors.BzrCommandError(
> 91 + 'Unrecognized merge tool: %s' % resolve_using)
> 92 + if not mergetools.check_availability(cmdline):
> 93 + raise errors.BzrCommandError(
> 94 + 'External merge tool is not available: %s' %
> 95 + resolve_using)
> 96 + mergetools.resolve_conflicts(tree.conflicts(), cmdline)
>
> Same remark, plus this code is screaming: 'put me in a helper !', you have almost the same block above. Or may be that block should just be embedded in resolve_conflicts ?

I did at one stage, I'll see if I can put it back in there.

> 132 + action = 'tool'
>
> Be bold ! s/tool/mergetools/ :)

:)

> 141 + conf = config.GlobalConfig()
> 142 + cmdline = conf.find_merge_tool(using)
> 143 + if cmdline is None:
> 144 + raise errors.BzrCommandError(
> 145 + 'Unrecognized merge tool: %s' % using)
> 146 + if not mergetools.check_availability(cmdline):
> 147 + raise errors.BzrCommandError(
> 148 + 'Merge tool is not available: %s' % using)
>
> Or may be it's really a distinct helper :) Whatever you chose, make sure a wt object is available, you use only GlobalConfig so far, but in the long term this would probably be a branch or wt config, both of which could be accessed via a wt.

Good point about the config. I'll change that.

> 161 + if verbose:
> 162 + ui.ui_factory.show_message('Invoking %s on %s...' %
> 163 + (merge_tool.get_name(), file))
>
> Hmm, until we re-think the way we resolve conflicts I'd prefer that the feedback is the same for all the different ways to resolve a conflict. This particular message looks more like a trace.mutter() to me.
>
> And since this is the only use of the 'verbose' option, I think we can just remove it.

Done.

> 168 + ui.ui_factory.show_message('%d conflict(s) resolved.' % resolved)
> 169 + unresolved = tree.conflicts()
> 170 + if len(unresolved) > 0:
> 171 + ui.ui_factory.show_message('Remaining conflicts:')
> 172 + for conflict in unresolved:
> 173 + ui.ui_factory.show_message(str(conflict))
>
> This seems to depart from the way the conflicts are reported in the other cases for no good reason, can you unify it instead ?

It's the same way that the 'auto' action reports it.

> More tests seem to be needed here, for testing the return codes of mergetools.resolve_conflicts with some simplified tool that can simulate resolving a conflict or not.

The tests for resolve_conflicts use a dummy invoker function which
doesn't actually call out to the subprocess. I can use this to fake retvals.

> An then some more blackbox tests to make sure its integration in resolve is correct too.

Ok.

lp:~doxxx/bzr/mergetools-commands updated
5421. By Gordon Tyler

Merged from bzr.dev.

5422. By Gordon Tyler

resolve_conflicts returns 0 or first non-zero retval from mergetool.

5423. By Gordon Tyler

Cleanup according to vila's suggestions.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> > This seems to depart from the way the conflicts are reported in the other
> cases for no good reason, can you unify it instead ?
>
> It's the same way that the 'auto' action reports it.

Sorry for being imprecise.

'auto' lists the conflicts resolved, I don't think you do.
The other cases mentions the number of remaining conflicts, I think you don't.

I'd like the 'auto' case to be fixed too but I haven't yet found the right balance there.

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

On 2/25/2011 7:35 AM, Vincent Ladeuil wrote:
>>> This seems to depart from the way the conflicts are reported in the other
>> cases for no good reason, can you unify it instead ?
>>
>> It's the same way that the 'auto' action reports it.
>
> Sorry for being imprecise.
>
> 'auto' lists the conflicts resolved, I don't think you do.

I do.

+ ui.ui_factory.show_message('%d conflict(s) resolved.' %
resolved)
+ unresolved = tree.conflicts()
+ if len(unresolved) > 0:
+ ui.ui_factory.show_message('Remaining conflicts:')
+ for conflict in unresolved:
+ ui.ui_factory.show_message(str(conflict))

> The other cases mentions the number of remaining conflicts, I think you don't.

If you would prefer just the number of remaining conflicts, I can do
that instead.

Revision history for this message
Vincent Ladeuil (vila) wrote :

> I do.
>
> + ui.ui_factory.show_message('%d conflict(s) resolved.' %
> resolved)
> + unresolved = tree.conflicts()
> + if len(unresolved) > 0:
> + ui.ui_factory.show_message('Remaining conflicts:')
> + for conflict in unresolved:
> + ui.ui_factory.show_message(str(conflict))
>

*blink*, I missed that, sorry.

Still, auto does:

                    for conflict in un_resolved:
                        trace.note(conflict)
                    return 1
                else:
                    trace.note('All conflicts resolved.')
                    return 0

So if you want to do the same, I'll be fine, but let's do exactly the same then, use trace.note() and return some corresponding value.

> > The other cases mentions the number of remaining conflicts, I think you
> don't.
>
> If you would prefer just the number of remaining conflicts, I can do
> that instead.

That's also an option, I think the idea with mentioning only the number is that it would satisfy users who prefer less verbose output and can still see the conflicts with 'bzr conflicts -v'.

In any case, addressing such unification can be done in a further submission as long as you don't introduce another variation (which you don't expect for the details raised above).

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

On 2/9/2011 9:37 AM, Vincent Ladeuil wrote:
> Or may be it's really a distinct helper :) Whatever you chose, make
> sure a wt object is available, you use only GlobalConfig so far, but
> in the long term this would probably be a branch or wt config, both
> of which could be accessed via a wt.

Is there a way to get a config from a wt object now? I looked but either
I'm looking in the wrong place or it's not currently available.

Anyway, I've extracted that code out into a helper which takes a
mergetool name and config object.

lp:~doxxx/bzr/mergetools-commands updated
5424. By Gordon Tyler

cmd_resolve: Changed mergetool user feedback to be consistent with auto mode.

5425. By Gordon Tyler

Make it clear that it's a merge tool action.

5426. By Gordon Tyler

Extracted helper function get_mergetool_cmdline.

Revision history for this message
Vincent Ladeuil (vila) wrote :

>>>>> Gordon Tyler <email address hidden> writes:

    > On 2/9/2011 9:37 AM, Vincent Ladeuil wrote:
    >> Or may be it's really a distinct helper :) Whatever you chose, make
    >> sure a wt object is available, you use only GlobalConfig so far, but
    >> in the long term this would probably be a branch or wt config, both
    >> of which could be accessed via a wt.

    > Is there a way to get a config from a wt object now?

No.

    > I looked but either I'm looking in the wrong place or it's not
    > currently available.

    > Anyway, I've extracted that code out into a helper which takes a
    > mergetool name and config object.

Don't forget to put your proposal status to 'Needs Review' when you're
happy with the result.

      Vincent

Unmerged revisions

5426. By Gordon Tyler

Extracted helper function get_mergetool_cmdline.

5425. By Gordon Tyler

Make it clear that it's a merge tool action.

5424. By Gordon Tyler

cmd_resolve: Changed mergetool user feedback to be consistent with auto mode.

5423. By Gordon Tyler

Cleanup according to vila's suggestions.

5422. By Gordon Tyler

resolve_conflicts returns 0 or first non-zero retval from mergetool.

5421. By Gordon Tyler

Merged from bzr.dev.

5420. By Gordon Tyler

Merged mergetools into mergetools-commands.

5419. By Gordon Tyler

Moved info from whats-new-in-2.3.txt to whats-new-in-2.4.txt.

5418. By Gordon Tyler

Merged mergetools into mergetools-commands.

5417. By Gordon Tyler

Moved mergetools-commands NEWS from bzr-2.3.txt to bzr-2.4.txt.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== modified file 'bzrlib/builtins.py'
--- bzrlib/builtins.py 2011-02-18 12:00:14 +0000
+++ bzrlib/builtins.py 2011-02-27 14:40:46 +0000
@@ -39,6 +39,7 @@
39 log,39 log,
40 merge as _mod_merge,40 merge as _mod_merge,
41 merge_directive,41 merge_directive,
42 mergetools,
42 osutils,43 osutils,
43 reconfigure,44 reconfigure,
44 rename_map,45 rename_map,
@@ -196,6 +197,17 @@
196 return bzrdir.BzrDir.open_containing_tree_or_branch(filename)197 return bzrdir.BzrDir.open_containing_tree_or_branch(filename)
197198
198199
200def get_mergetool_cmdline(tool_name, config):
201 cmdline = config.find_merge_tool(tool_name)
202 if cmdline is None:
203 raise errors.BzrCommandError(
204 'Unrecognized merge tool: %s' % tool_name)
205 if not mergetools.check_availability(cmdline):
206 raise errors.BzrCommandError(
207 'External merge tool is not available: %s' % tool_name)
208 return cmdline
209
210
199# TODO: Make sure no commands unconditionally use the working directory as a211# TODO: Make sure no commands unconditionally use the working directory as a
200# branch. If a filename argument is used, the first of them should be used to212# branch. If a filename argument is used, the first of them should be used to
201# specify the branch. (Perhaps this can be factored out into some kind of213# specify the branch. (Perhaps this can be factored out into some kind of
@@ -3942,7 +3954,9 @@
3942 Option('preview', help='Instead of merging, show a diff of the'3954 Option('preview', help='Instead of merging, show a diff of the'
3943 ' merge.'),3955 ' merge.'),
3944 Option('interactive', help='Select changes interactively.',3956 Option('interactive', help='Select changes interactively.',
3945 short_name='i')3957 short_name='i'),
3958 Option('resolve-using', help='Invokes the external merge tool named '
3959 'ARG for each merged file with conflicts.', type=str),
3946 ]3960 ]
39473961
3948 def run(self, location=None, revision=None, force=False,3962 def run(self, location=None, revision=None, force=False,
@@ -3951,6 +3965,7 @@
3951 directory=None,3965 directory=None,
3952 preview=False,3966 preview=False,
3953 interactive=False,3967 interactive=False,
3968 resolve_using=None
3954 ):3969 ):
3955 if merge_type is None:3970 if merge_type is None:
3956 merge_type = _mod_merge.Merge3Merger3971 merge_type = _mod_merge.Merge3Merger
@@ -4027,12 +4042,17 @@
4027 "This branch has no commits."4042 "This branch has no commits."
4028 " (perhaps you would prefer 'bzr pull')")4043 " (perhaps you would prefer 'bzr pull')")
4029 if preview:4044 if preview:
4030 return self._do_preview(merger)4045 retval = self._do_preview(merger)
4031 elif interactive:4046 elif interactive:
4032 return self._do_interactive(merger)4047 retval = self._do_interactive(merger)
4033 else:4048 else:
4034 return self._do_merge(merger, change_reporter, allow_pending,4049 retval = self._do_merge(merger, change_reporter, allow_pending,
4035 verified)4050 verified)
4051 if retval != 0 and resolve_using is not None:
4052 config = _mod_config.GlobalConfig()
4053 cmdline = get_mergetool_cmdline(resolve_using, config)
4054 retval = mergetools.resolve_conflicts(tree.conflicts(), cmdline)
4055 return retval
40364056
4037 def _get_preview(self, merger):4057 def _get_preview(self, merger):
4038 tree_merger = merger.make_merger()4058 tree_merger = merger.make_merger()
@@ -4231,14 +4251,16 @@
4231 """4251 """
4232 takes_args = ['file*']4252 takes_args = ['file*']
4233 takes_options = [4253 takes_options = [
4234 'merge-type',4254 'merge-type',
4235 'reprocess',4255 'reprocess',
4236 Option('show-base',4256 Option('show-base',
4237 help="Show base revision text in conflicts."),4257 help="Show base revision text in conflicts."),
4238 ]4258 Option('resolve-using', help='Invokes the external merge tool named '
4259 'ARG for each merged file with conflicts.', type=str),
4260 ]
42394261
4240 def run(self, file_list=None, merge_type=None, show_base=False,4262 def run(self, file_list=None, merge_type=None, show_base=False,
4241 reprocess=False):4263 reprocess=False, resolve_using=None):
4242 from bzrlib.conflicts import restore4264 from bzrlib.conflicts import restore
4243 if merge_type is None:4265 if merge_type is None:
4244 merge_type = _mod_merge.Merge3Merger4266 merge_type = _mod_merge.Merge3Merger
@@ -4296,9 +4318,15 @@
4296 finally:4318 finally:
4297 tree.set_parent_ids(parents)4319 tree.set_parent_ids(parents)
4298 if conflicts > 0:4320 if conflicts > 0:
4299 return 14321 if resolve_using is not None:
4322 config = _mod_config.GlobalConfig()
4323 cmdline = get_mergetool_cmdline(resolve_using, config)
4324 retval = mergetools.resolve_conflicts(tree.conflicts(), cmdline)
4325 else:
4326 retval = 1
4300 else:4327 else:
4301 return 04328 retval = 0
4329 return retval
43024330
43034331
4304class cmd_revert(Command):4332class cmd_revert(Command):
43054333
=== modified file 'bzrlib/conflicts.py'
--- bzrlib/conflicts.py 2011-02-08 13:56:49 +0000
+++ bzrlib/conflicts.py 2011-02-27 14:40:46 +0000
@@ -26,7 +26,9 @@
26from bzrlib import (26from bzrlib import (
27 cleanup,27 cleanup,
28 commands,28 commands,
29 config,
29 errors,30 errors,
31 mergetools,
30 osutils,32 osutils,
31 rio,33 rio,
32 trace,34 trace,
@@ -115,9 +117,14 @@
115 'directory',117 'directory',
116 option.Option('all', help='Resolve all conflicts in this tree.'),118 option.Option('all', help='Resolve all conflicts in this tree.'),
117 ResolveActionOption(),119 ResolveActionOption(),
120 option.Option('using', help='Resolve conflicts using an '
121 'external merge tool.', type=str),
118 ]122 ]
119 _see_also = ['conflicts']123 _see_also = ['conflicts']
120 def run(self, file_list=None, all=False, action=None, directory=None):124 def run(self, file_list=None, all=False, action=None, directory=None,
125 using=None):
126 if using is not None:
127 action = 'mergetool'
121 if all:128 if all:
122 if file_list:129 if file_list:
123 raise errors.BzrCommandError("If --all is specified,"130 raise errors.BzrCommandError("If --all is specified,"
@@ -158,6 +165,43 @@
158 # refactoring to transfer tree.auto_resolve() to165 # refactoring to transfer tree.auto_resolve() to
159 # conflict.auto(tree) --vila 091242166 # conflict.auto(tree) --vila 091242
160 pass167 pass
168 elif action == 'mergetool':
169 conf = config.GlobalConfig()
170 cmdline = conf.find_merge_tool(using)
171 if cmdline is None:
172 raise errors.BzrCommandError(
173 'Unrecognized merge tool: %s' % using)
174 if not mergetools.check_availability(cmdline):
175 raise errors.BzrCommandError(
176 'Merge tool is not available: %s' % using)
177 if all:
178 file_list = []
179 for conflict in tree.conflicts():
180 file_list.append(conflict.path)
181 if file_list is None:
182 raise errors.BzrCommandError(
183 'Either FILE(s) or --all must be provided')
184 resolved = 0
185 for file in file_list:
186 # to avoid unnecessary './' prefix on file names
187 if directory != u'.' and directory is not None:
188 file = os.path.join(directory, file)
189 trace.mutter('Invoking mergetool %r on %r...' %
190 (merge_tool.get_name(), file))
191 retval = mergetools.invoke(cmdline, file)
192 if retval == 0:
193 resolve(tree, [file])
194 resolved += 1
195 unresolved = tree.conflicts()
196 if len(unresolved) > 0:
197 trace.note('%d conflict(s) resolved.' % resolved)
198 trace.note('Remaining conflicts:')
199 for conflict in unresolved:
200 trace.note(str(conflict))
201 return 1
202 else:
203 trace.note('All conflicts resolved.')
204 return 0
161 else:205 else:
162 before, after = resolve(tree, file_list, action=action)206 before, after = resolve(tree, file_list, action=action)
163 trace.note('%d conflict(s) resolved, %d remaining'207 trace.note('%d conflict(s) resolved, %d remaining'
164208
=== modified file 'bzrlib/mergetools.py'
--- bzrlib/mergetools.py 2011-01-21 23:51:15 +0000
+++ bzrlib/mergetools.py 2011-02-27 14:40:46 +0000
@@ -31,6 +31,8 @@
31)31)
32""")32""")
3333
34from bzrlib.conflicts import TextConflict
35
3436
35known_merge_tools = {37known_merge_tools = {
36 'bcompare': 'bcompare {this} {other} {base} {result}',38 'bcompare': 'bcompare {this} {other} {base} {result}',
@@ -68,9 +70,9 @@
68 invoker = subprocess_invoker70 invoker = subprocess_invoker
69 cmd_list = cmdline.split(command_line)71 cmd_list = cmdline.split(command_line)
70 args, tmp_file = _subst_filename(cmd_list, filename)72 args, tmp_file = _subst_filename(cmd_list, filename)
71 def cleanup(retcode):73 def cleanup(retval):
72 if tmp_file is not None:74 if tmp_file is not None:
73 if retcode == 0: # on success, replace file with temp file75 if retval == 0: # on success, replace file with temp file
74 shutil.move(tmp_file, filename)76 shutil.move(tmp_file, filename)
75 else: # otherwise, delete temp file77 else: # otherwise, delete temp file
76 os.remove(tmp_file)78 os.remove(tmp_file)
@@ -111,6 +113,17 @@
111113
112114
113def subprocess_invoker(executable, args, cleanup):115def subprocess_invoker(executable, args, cleanup):
114 retcode = subprocess.call([executable] + args)116 retval = subprocess.call([executable] + args)
115 cleanup(retcode)117 cleanup(retval)
116 return retcode118 return retval
119
120
121def resolve_conflicts(conflicts, command_line, invoker=None):
122 for conflict in conflicts:
123 if isinstance(conflict, TextConflict):
124 retval = invoke(command_line, conflict.path, invoker=invoker)
125 if retval != 0:
126 trace.mutter('resolve_conflicts: %r exited with retval %d; '
127 'aborting' % (command_line, retval))
128 return retval
129 return 0
117130
=== modified file 'bzrlib/tests/test_mergetools.py'
--- bzrlib/tests/test_mergetools.py 2011-01-20 04:44:14 +0000
+++ bzrlib/tests/test_mergetools.py 2011-02-27 14:40:46 +0000
@@ -24,6 +24,7 @@
24 tests24 tests
25)25)
26from bzrlib.tests.features import backslashdir_feature26from bzrlib.tests.features import backslashdir_feature
27from bzrlib.tests.test_conflicts import example_conflicts
2728
2829
29class TestFilenameSubstitution(tests.TestCaseInTempDir):30class TestFilenameSubstitution(tests.TestCaseInTempDir):
@@ -165,3 +166,17 @@
165 self.assertEqual(1, retcode)166 self.assertEqual(1, retcode)
166 self.assertEqual('tool', self._exe)167 self.assertEqual('tool', self._exe)
167 self.assertFileEqual('stuff', 'test.txt')168 self.assertFileEqual('stuff', 'test.txt')
169
170
171class TestResolve(tests.TestCaseInTempDir):
172
173 def test_resolve(self):
174 self._commandlines = []
175 def dummy_invoker(exe, args, cleanup):
176 self._commandlines.append((exe, args))
177 cleanup(0)
178 return 0
179 retval = mergetools.resolve_conflicts(example_conflicts, 'tool {result}',
180 invoker=dummy_invoker)
181 self.assertEqual([(u'tool', [u'p\xe5tha'])], self._commandlines)
182 self.assertEqual(0, retval)
168183
=== modified file 'doc/en/release-notes/bzr-2.4.txt'
--- doc/en/release-notes/bzr-2.4.txt 2011-02-25 02:01:51 +0000
+++ doc/en/release-notes/bzr-2.4.txt 2011-02-27 14:40:46 +0000
@@ -26,6 +26,10 @@
26* External merge tools can now be configured in bazaar.conf. See26* External merge tools can now be configured in bazaar.conf. See
27 ``bzr help configuration`` for more information. (Gordon Tyler, #489915)27 ``bzr help configuration`` for more information. (Gordon Tyler, #489915)
2828
29* Added ``--resolve-using`` option to ``bzr merge`` and ``bzr remerge``
30 commands to invoke an external merge too to resolve text conflicts.
31 (Gordon Tyler, #489915)
32
29* Configuration options can now use references to other options in the same33* Configuration options can now use references to other options in the same
30 file by enclosing them with curly brackets (``{other_opt}``). This makes it34 file by enclosing them with curly brackets (``{other_opt}``). This makes it
31 possible to use, for example,35 possible to use, for example,
3236
=== modified file 'doc/en/whats-new/whats-new-in-2.4.txt'
--- doc/en/whats-new/whats-new-in-2.4.txt 2011-02-25 00:15:23 +0000
+++ doc/en/whats-new/whats-new-in-2.4.txt 2011-02-27 14:40:46 +0000
@@ -23,6 +23,11 @@
23and commandline of one or more external merge tools can be defined in23and commandline of one or more external merge tools can be defined in
24bazaar.conf. See the help topic ``configuration`` for more details.24bazaar.conf. See the help topic ``configuration`` for more details.
2525
26External merge tools can be invoked for files with text conflicts during
27``bzr merge`` and ``bzr remerge`` operations by adding the ``--resolve-using``
28option. They can also be used to resolve a text conflict with the ``--using``
29option on the ``bzr resolve`` command.
30
26Tagged Revisions are Copied31Tagged Revisions are Copied
27***************************32***************************
2833