Patchwork [1,of,2] merge: use labels in prompts to the user

login
register
mail settings
Submitter Simon Farnsworth
Date Aug. 12, 2016, 1:50 p.m.
Message ID <6d3af00243808b1be9f2.1471009851@devvm631.lla1.facebook.com>
Download mbox | patch
Permalink /patch/16262/
State Accepted
Headers show

Comments

Simon Farnsworth - Aug. 12, 2016, 1:50 p.m.
# HG changeset patch
# User Simon Farnsworth <simonfar@fb.com>
# Date 1471006902 25200
#      Fri Aug 12 06:01:42 2016 -0700
# Node ID 6d3af00243808b1be9f27a5545be0fce0e43d1f2
# Parent  37b6f0ec6241a62de90737409458cd622e2fac0d
merge: use labels in prompts to the user

Now that we persist the labels, we can consistently use the labels in
prompts for the user without risk of confusion. This changes a huge amount
of command output:

This means that merge prompts like:
  no tool found to merge a
  keep (l)ocal, take (o)ther, or leave (u)nresolved? u
and
  remote changed a which local deleted
  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
become:
  no tool found to merge a
  keep (l)ocal [working copy], take (o)ther [destination], or leave (u)nresolved? u
and
  remote [source] changed a which local [dest] deleted
  use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
where "working copy" and "destination" were supplied by the command that
requested the merge as labels for conflict markers, and thus should be
human-friendly.
Simon Farnsworth - Aug. 12, 2016, 4:41 p.m.
I missed the v4 tag on this patchset.

I'm going to leave it for now - I'll resend next week if I see no review 
comments.

Simon

On 12/08/2016 14:50, Simon Farnsworth wrote:
> # HG changeset patch
> # User Simon Farnsworth <simonfar@fb.com>
> # Date 1471006902 25200
> #      Fri Aug 12 06:01:42 2016 -0700
> # Node ID 6d3af00243808b1be9f27a5545be0fce0e43d1f2
> # Parent  37b6f0ec6241a62de90737409458cd622e2fac0d
> merge: use labels in prompts to the user
>
> Now that we persist the labels, we can consistently use the labels in
> prompts for the user without risk of confusion. This changes a huge amount
> of command output:
>
> This means that merge prompts like:
>   no tool found to merge a
>   keep (l)ocal, take (o)ther, or leave (u)nresolved? u
> and
>   remote changed a which local deleted
>   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
> become:
>   no tool found to merge a
>   keep (l)ocal [working copy], take (o)ther [destination], or leave (u)nresolved? u
> and
>   remote [source] changed a which local [dest] deleted
>   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
> where "working copy" and "destination" were supplied by the command that
> requested the merge as labels for conflict markers, and thus should be
> human-friendly.
>
> diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
> --- a/mercurial/filemerge.py
> +++ b/mercurial/filemerge.py
> @@ -230,50 +230,56 @@
>                  util.writefile(file, newdata)
>
>  @internaltool('prompt', nomerge)
> -def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
> +def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
>      """Asks the user which of the local `p1()` or the other `p2()` version to
>      keep as the merged version."""
>      ui = repo.ui
>      fd = fcd.path()
>
> +    prompts = partextras(labels)
> +    prompts['fd'] = fd
>      try:
>          if fco.isabsent():
>              index = ui.promptchoice(
> -                _("local changed %s which remote deleted\n"
> +                _("local%(l)s changed %(fd)s which remote%(o)s deleted\n"
>                    "use (c)hanged version, (d)elete, or leave (u)nresolved?"
> -                  "$$ &Changed $$ &Delete $$ &Unresolved") % fd, 2)
> +                  "$$ &Changed $$ &Delete $$ &Unresolved") % prompts, 2)
>              choice = ['local', 'other', 'unresolved'][index]
>          elif fcd.isabsent():
>              index = ui.promptchoice(
> -                _("remote changed %s which local deleted\n"
> +                _("remote%(o)s changed %(fd)s which local%(l)s deleted\n"
>                    "use (c)hanged version, leave (d)eleted, or "
>                    "leave (u)nresolved?"
> -                  "$$ &Changed $$ &Deleted $$ &Unresolved") % fd, 2)
> +                  "$$ &Changed $$ &Deleted $$ &Unresolved") % prompts, 2)
>              choice = ['other', 'local', 'unresolved'][index]
>          else:
>              index = ui.promptchoice(
> -                _("no tool found to merge %s\n"
> -                  "keep (l)ocal, take (o)ther, or leave (u)nresolved?"
> -                  "$$ &Local $$ &Other $$ &Unresolved") % fd, 2)
> +                _("no tool found to merge %(fd)s\n"
> +                  "keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved?"
> +                  "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
>              choice = ['local', 'other', 'unresolved'][index]
>
>          if choice == 'other':
> -            return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
> +            return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
> +                           labels)
>          elif choice == 'local':
> -            return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
> +            return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
> +                           labels)
>          elif choice == 'unresolved':
> -            return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf)
> +            return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
> +                          labels)
>      except error.ResponseExpected:
>          ui.write("\n")
> -        return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf)
> +        return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
> +                      labels)
>
>  @internaltool('local', nomerge)
> -def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
> +def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
>      """Uses the local `p1()` version of files as the merged version."""
>      return 0, fcd.isabsent()
>
>  @internaltool('other', nomerge)
> -def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
> +def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
>      """Uses the other `p2()` version of files as the merged version."""
>      if fco.isabsent():
>          # local changed, remote deleted -- 'deleted' picked
> @@ -285,7 +291,7 @@
>      return 0, deleted
>
>  @internaltool('fail', nomerge)
> -def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
> +def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
>      """
>      Rather than attempting to merge files that were modified on both
>      branches, it marks them as unresolved. The resolve command must be
> @@ -537,6 +543,22 @@
>          newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
>      return newlabels
>
> +def partextras(labels):
> +    """Return a dictionary of extra labels for use in prompts to the user
> +
> +    Intended use is in strings of the form "(l)ocal%(l)s".
> +    """
> +    if labels is None:
> +        return {
> +            "l": "",
> +            "o": "",
> +        }
> +
> +    return {
> +        "l": " [%s]" % labels[0],
> +        "o": " [%s]" % labels[1],
> +    }
> +
>  def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
>      """perform a 3-way merge in the working directory
>
> @@ -588,7 +610,7 @@
>      toolconf = tool, toolpath, binary, symlink
>
>      if mergetype == nomerge:
> -        r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf)
> +        r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
>          return True, r, deleted
>
>      if premerge:
> diff --git a/mercurial/merge.py b/mercurial/merge.py
> --- a/mercurial/merge.py
> +++ b/mercurial/merge.py
> @@ -1535,11 +1535,13 @@
>          if '.hgsubstate' in actionbyfile:
>              f = '.hgsubstate'
>              m, args, msg = actionbyfile[f]
> +            prompts = filemerge.partextras(labels)
> +            prompts['f'] = f
>              if m == 'cd':
>                  if repo.ui.promptchoice(
> -                    _("local changed %s which remote deleted\n"
> +                    _("local%(l)s changed %(f)s which remote%(o)s deleted\n"
>                        "use (c)hanged version or (d)elete?"
> -                      "$$ &Changed $$ &Delete") % f, 0):
> +                      "$$ &Changed $$ &Delete") % prompts, 0):
>                      actionbyfile[f] = ('r', None, "prompt delete")
>                  elif f in p1:
>                      actionbyfile[f] = ('am', None, "prompt keep")
> @@ -1549,9 +1551,9 @@
>                  f1, f2, fa, move, anc = args
>                  flags = p2[f2].flags()
>                  if repo.ui.promptchoice(
> -                    _("remote changed %s which local deleted\n"
> +                    _("remote%(o)s changed %(f)s which local%(l)s deleted\n"
>                        "use (c)hanged version or leave (d)eleted?"
> -                      "$$ &Changed $$ &Deleted") % f, 0) == 0:
> +                      "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
>                      actionbyfile[f] = ('g', (flags, False), "prompt recreating")
>                  else:
>                      del actionbyfile[f]
> diff --git a/tests/failfilemerge.py b/tests/failfilemerge.py
> --- a/tests/failfilemerge.py
> +++ b/tests/failfilemerge.py
> @@ -9,7 +9,7 @@
>  )
>
>  def failfilemerge(filemergefn,
> -        premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
> +                  premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
>      raise error.Abort("^C")
>      return filemergefn(premerge, repo, mynode, orig, fcd, fco, fca, labels)
>
> diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t
> --- a/tests/test-copy-move-merge.t
> +++ b/tests/test-copy-move-merge.t
> @@ -85,7 +85,7 @@
>    > c
>    > EOF
>    rebasing 2:add3f11052fa "other" (tip)
> -  remote changed a which local deleted
> +  remote [source] changed a which local [dest] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
>
>    $ cat b
> diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t
> --- a/tests/test-largefiles-update.t
> +++ b/tests/test-largefiles-update.t
> @@ -611,7 +611,7 @@
>    > EOF
>    rebasing 1:72518492caa6 "#1"
>    rebasing 4:07d6153b5c04 "#4"
> -  local changed .hglf/large1 which remote deleted
> +  local [dest] changed .hglf/large1 which remote [source] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved? c
>
>    $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
> diff --git a/tests/test-merge-changedelete.t b/tests/test-merge-changedelete.t
> --- a/tests/test-merge-changedelete.t
> +++ b/tests/test-merge-changedelete.t
> @@ -717,9 +717,9 @@
>    $ echo changed >> file1
>    $ hg rm file2
>    $ hg update 1 -y
> -  local changed file1 which remote deleted
> +  local [working copy] changed file1 which remote [destination] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved? u
> -  remote changed file2 which local deleted
> +  remote [destination] changed file2 which local [working copy] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
>    1 files updated, 0 files merged, 0 files removed, 2 files unresolved
>    use 'hg resolve' to retry unresolved file merges
> @@ -893,9 +893,9 @@
>    $ echo changed >> file1
>    $ hg rm file2
>    $ hg update 1 --config ui.interactive=True --tool :prompt
> -  local changed file1 which remote deleted
> +  local [working copy] changed file1 which remote [destination] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved?
> -  remote changed file2 which local deleted
> +  remote [destination] changed file2 which local [working copy] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved?
>    1 files updated, 0 files merged, 0 files removed, 2 files unresolved
>    use 'hg resolve' to retry unresolved file merges
> @@ -943,9 +943,9 @@
>    $ echo changed >> file1
>    $ hg rm file2
>    $ hg update 1 --tool :merge3
> -  local changed file1 which remote deleted
> +  local [working copy] changed file1 which remote [destination] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved? u
> -  remote changed file2 which local deleted
> +  remote [destination] changed file2 which local [working copy] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
>    1 files updated, 0 files merged, 0 files removed, 2 files unresolved
>    use 'hg resolve' to retry unresolved file merges
> @@ -999,9 +999,9 @@
>    (status identical)
>
>    === :other -> :prompt ===
> -  local changed file1 which remote deleted
> +  local [working copy] changed file1 which remote [destination] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved?
> -  remote changed file2 which local deleted
> +  remote [destination] changed file2 which local [working copy] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved?
>    --- diff of status ---
>    (status identical)
> @@ -1026,9 +1026,9 @@
>    (status identical)
>
>    === :local -> :prompt ===
> -  local changed file1 which remote deleted
> +  local [working copy] changed file1 which remote [destination] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved?
> -  remote changed file2 which local deleted
> +  remote [destination] changed file2 which local [working copy] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved?
>    --- diff of status ---
>    (status identical)
> @@ -1043,9 +1043,9 @@
>    (status identical)
>
>    === :fail -> :prompt ===
> -  local changed file1 which remote deleted
> +  local [working copy] changed file1 which remote [destination] deleted
>    use (c)hanged version, (d)elete, or leave (u)nresolved?
> -  remote changed file2 which local deleted
> +  remote [destination] changed file2 which local [working copy] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved?
>    --- diff of status ---
>    (status identical)
> diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t
> --- a/tests/test-merge-types.t
> +++ b/tests/test-merge-types.t
> @@ -173,7 +173,7 @@
>    (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
>    picked tool ':prompt' for a (binary False symlink True changedelete False)
>    no tool found to merge a
> -  keep (l)ocal, take (o)ther, or leave (u)nresolved? u
> +  keep (l)ocal [working copy], take (o)ther [destination], or leave (u)nresolved? u
>    0 files updated, 0 files merged, 0 files removed, 1 files unresolved
>    use 'hg resolve' to retry unresolved file merges
>    1 other heads for branch "default"
> diff --git a/tests/test-rebase-newancestor.t b/tests/test-rebase-newancestor.t
> --- a/tests/test-rebase-newancestor.t
> +++ b/tests/test-rebase-newancestor.t
> @@ -135,7 +135,7 @@
>    note: rebase of 1:1d1a643d390e created no changes to commit
>    rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
>    rebasing 4:4b019212aaf6 "dev: merge default"
> -  remote changed f-default which local deleted
> +  remote [source] changed f-default which local [dest] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
>    rebasing 6:9455ee510502 "dev: merge default"
>    saved backup bundle to $TESTTMP/ancestor-merge/.hg/strip-backup/1d1a643d390e-43e9e04b-backup.hg (glob)
> @@ -164,7 +164,7 @@
>    > EOF
>    rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
>    rebasing 4:4b019212aaf6 "dev: merge default"
> -  remote changed f-default which local deleted
> +  remote [source] changed f-default which local [dest] deleted
>    use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
>    rebasing 6:9455ee510502 "dev: merge default"
>    saved backup bundle to $TESTTMP/ancestor-merge-2/.hg/strip-backup/ec2c14fb2984-62d0b222-backup.hg (glob)
> diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t
> --- a/tests/test-subrepo-missing.t
> +++ b/tests/test-subrepo-missing.t
> @@ -62,7 +62,7 @@
>    2 files updated, 0 files merged, 0 files removed, 0 files unresolved
>    $ rm .hgsubstate
>    $ hg up 0
> -  remote changed .hgsubstate which local deleted
> +  remote [destination] changed .hgsubstate which local [working copy] deleted
>    use (c)hanged version or leave (d)eleted? c
>    1 files updated, 0 files merged, 0 files removed, 0 files unresolved
>    $ hg st
> _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://urldefense.proofpoint.com/v2/url?u=https-3A__www.mercurial-2Dscm.org_mailman_listinfo_mercurial-2Ddevel&d=CwIGaQ&c=5VD0RTtNlTh3ycd41b3MUw&r=mEgSWILcY4c4W3zjApBQLA&m=VIhAb2OCGLvrWY-7ln1AMUdy2Ysigpo2Fx2RL_fhGXw&s=ejYlRS5ya-vExpQ-7rMQjv68I0VL-HaOfVDbhMkYl8w&e=
>
Yuya Nishihara - Aug. 13, 2016, 10:45 a.m.
On Fri, 12 Aug 2016 06:50:51 -0700, Simon Farnsworth wrote:
> # HG changeset patch
> # User Simon Farnsworth <simonfar@fb.com>
> # Date 1471006902 25200
> #      Fri Aug 12 06:01:42 2016 -0700
> # Node ID 6d3af00243808b1be9f27a5545be0fce0e43d1f2
> # Parent  37b6f0ec6241a62de90737409458cd622e2fac0d
> merge: use labels in prompts to the user

Looks great. Queued the series, thanks.
Katsunori FUJIWARA - Aug. 13, 2016, 12:54 p.m.
At Sat, 13 Aug 2016 19:45:53 +0900,
Yuya Nishihara wrote:
> 
> On Fri, 12 Aug 2016 06:50:51 -0700, Simon Farnsworth wrote:
> > # HG changeset patch
> > # User Simon Farnsworth <simonfar@fb.com>
> > # Date 1471006902 25200
> > #      Fri Aug 12 06:01:42 2016 -0700
> > # Node ID 6d3af00243808b1be9f27a5545be0fce0e43d1f2
> > # Parent  37b6f0ec6241a62de90737409458cd622e2fac0d
> > merge: use labels in prompts to the user
> 
> Looks great. Queued the series, thanks.

As a message translator, I want any suggestion around usage of labels.

> > Now that we persist the labels, we can consistently use the labels in
> > prompts for the user without risk of confusion. This changes a huge amount
> > of command output:
> > 
> > This means that merge prompts like:
> >   no tool found to merge a
> >   keep (l)ocal, take (o)ther, or leave (u)nresolved? u
> > and
> >   remote changed a which local deleted
> >   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
> > become:
> >   no tool found to merge a
> >   keep (l)ocal [working copy], take (o)ther [destination], or leave (u)nresolved? u
> > and
> >   remote [source] changed a which local [dest] deleted
> >   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
> > where "working copy" and "destination" were supplied by the command that
> > requested the merge as labels for conflict markers, and thus should be
> > human-friendly.

At this change, texts specified via "labels" argument aren't i18n-ed
intentionally, because using translated texts as a part of conflict
mark might cause mixed encoding in merged file (e.g. iso-2022-jp,
cp932 aka shift-jis, euc-jp and utf-8 are famouse encoding for
Japanese texts).

If labels are shown in prompt message after this patch, we should
translate them only at prompting, to increase readability for l10n-ed
prompting message. At least in Japanese, plain "[working copy]",
"[source]" or "[destination]" in translated message looks very
strange.

But, on the other hand, I'm afraid that translation of a generic word
like above might cause confusion in the future.

For example:

  1. "destination" is used in the context of "hg merge" or so

  2. translator translates it for "hg merge" context

     BTW, I often translate it as "destination (revision) at
     updating" or so, for reasonable phrasing.

  3. change after translation makes "destination" be used also in the
     context of "hg push"

  4. i18n tool unifies both "destination" silently

  5. translator doesn't notice that "destination" is used in other
     than the context of "hg merge"

  6. strange translation appears at "hg push"

     BTW, I often translate it as "destination (repository) at
     changeset outgoing" or so.

At least, I don't know reasonable context-free translation of
"destination" in Japanese in such case (direct translation causes
strange phrasing)

How about other languages ?

Making labels more descriptive would resolve this problem (e.g.
"destination revision" instead of "destination"). But it makes
prompting message in English very redundant, doesn't it ?

Or introducing "gettext with context" ? :-)

  # i18n: "destination" at merging. omit after "@" at translation
  l10ntext = _c('destination@merging')
  # _c() returns "destination", if there is no translation
  # corresponded to "destination@merging" msgid


 _______________________________________________
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel

----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp
Yuya Nishihara - Aug. 14, 2016, 1:08 a.m.
On Sat, 13 Aug 2016 21:54:11 +0900, FUJIWARA Katsunori wrote:
> Making labels more descriptive would resolve this problem (e.g.
> "destination revision" instead of "destination"). But it makes
> prompting message in English very redundant, doesn't it ?
> 
> Or introducing "gettext with context" ? :-)
> 
>   # i18n: "destination" at merging. omit after "@" at translation
>   l10ntext = _c('destination@merging')
>   # _c() returns "destination", if there is no translation
>   # corresponded to "destination@merging" msgid

You can specify msgctxt by 2nd argument of _() for example, and pass
"--keyword=_:1,2c" or "--keyword=_:1,2c,2t" to xgettext to collect them.

https://developer.mozilla.org/en-US/docs/gettext#Using_context_with_msgctxt
https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/xgettext-Invocation.html

msgctxt is concatenated with '\004'.

https://github.com/django/django/blob/master/django/utils/translation/trans_real.py#L346
Katsunori FUJIWARA - Aug. 15, 2016, 1:41 p.m.
At Sun, 14 Aug 2016 10:08:20 +0900,
Yuya Nishihara wrote:
> 
> On Sat, 13 Aug 2016 21:54:11 +0900, FUJIWARA Katsunori wrote:
> > Making labels more descriptive would resolve this problem (e.g.
> > "destination revision" instead of "destination"). But it makes
> > prompting message in English very redundant, doesn't it ?
> > 
> > Or introducing "gettext with context" ? :-)
> > 
> >   # i18n: "destination" at merging. omit after "@" at translation
> >   l10ntext = _c('destination@merging')
> >   # _c() returns "destination", if there is no translation
> >   # corresponded to "destination@merging" msgid
> 
> You can specify msgctxt by 2nd argument of _() for example, and pass
> "--keyword=_:1,2c" or "--keyword=_:1,2c,2t" to xgettext to collect them.
> 
> https://developer.mozilla.org/en-US/docs/gettext#Using_context_with_msgctxt
> https://www.gnu.org/savannah-checkouts/gnu/gettext/manual/html_node/xgettext-Invocation.html
> 
> msgctxt is concatenated with '\004'.
> 
> https://github.com/django/django/blob/master/django/utils/translation/trans_real.py#L346
> 

Oh, thank you for information !

I'll work for "gettext with context".

----------------------------------------------------------------------
[FUJIWARA Katsunori]                             foozy@lares.dti.ne.jp

Patch

diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py
--- a/mercurial/filemerge.py
+++ b/mercurial/filemerge.py
@@ -230,50 +230,56 @@ 
                 util.writefile(file, newdata)
 
 @internaltool('prompt', nomerge)
-def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _iprompt(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """Asks the user which of the local `p1()` or the other `p2()` version to
     keep as the merged version."""
     ui = repo.ui
     fd = fcd.path()
 
+    prompts = partextras(labels)
+    prompts['fd'] = fd
     try:
         if fco.isabsent():
             index = ui.promptchoice(
-                _("local changed %s which remote deleted\n"
+                _("local%(l)s changed %(fd)s which remote%(o)s deleted\n"
                   "use (c)hanged version, (d)elete, or leave (u)nresolved?"
-                  "$$ &Changed $$ &Delete $$ &Unresolved") % fd, 2)
+                  "$$ &Changed $$ &Delete $$ &Unresolved") % prompts, 2)
             choice = ['local', 'other', 'unresolved'][index]
         elif fcd.isabsent():
             index = ui.promptchoice(
-                _("remote changed %s which local deleted\n"
+                _("remote%(o)s changed %(fd)s which local%(l)s deleted\n"
                   "use (c)hanged version, leave (d)eleted, or "
                   "leave (u)nresolved?"
-                  "$$ &Changed $$ &Deleted $$ &Unresolved") % fd, 2)
+                  "$$ &Changed $$ &Deleted $$ &Unresolved") % prompts, 2)
             choice = ['other', 'local', 'unresolved'][index]
         else:
             index = ui.promptchoice(
-                _("no tool found to merge %s\n"
-                  "keep (l)ocal, take (o)ther, or leave (u)nresolved?"
-                  "$$ &Local $$ &Other $$ &Unresolved") % fd, 2)
+                _("no tool found to merge %(fd)s\n"
+                  "keep (l)ocal%(l)s, take (o)ther%(o)s, or leave (u)nresolved?"
+                  "$$ &Local $$ &Other $$ &Unresolved") % prompts, 2)
             choice = ['local', 'other', 'unresolved'][index]
 
         if choice == 'other':
-            return _iother(repo, mynode, orig, fcd, fco, fca, toolconf)
+            return _iother(repo, mynode, orig, fcd, fco, fca, toolconf,
+                           labels)
         elif choice == 'local':
-            return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf)
+            return _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf,
+                           labels)
         elif choice == 'unresolved':
-            return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf)
+            return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
+                          labels)
     except error.ResponseExpected:
         ui.write("\n")
-        return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf)
+        return _ifail(repo, mynode, orig, fcd, fco, fca, toolconf,
+                      labels)
 
 @internaltool('local', nomerge)
-def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _ilocal(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """Uses the local `p1()` version of files as the merged version."""
     return 0, fcd.isabsent()
 
 @internaltool('other', nomerge)
-def _iother(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _iother(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """Uses the other `p2()` version of files as the merged version."""
     if fco.isabsent():
         # local changed, remote deleted -- 'deleted' picked
@@ -285,7 +291,7 @@ 
     return 0, deleted
 
 @internaltool('fail', nomerge)
-def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf):
+def _ifail(repo, mynode, orig, fcd, fco, fca, toolconf, labels=None):
     """
     Rather than attempting to merge files that were modified on both
     branches, it marks them as unresolved. The resolve command must be
@@ -537,6 +543,22 @@ 
         newlabels.append(_formatconflictmarker(repo, ca, tmpl, labels[2], pad))
     return newlabels
 
+def partextras(labels):
+    """Return a dictionary of extra labels for use in prompts to the user
+
+    Intended use is in strings of the form "(l)ocal%(l)s".
+    """
+    if labels is None:
+        return {
+            "l": "",
+            "o": "",
+        }
+
+    return {
+        "l": " [%s]" % labels[0],
+        "o": " [%s]" % labels[1],
+    }
+
 def _filemerge(premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
     """perform a 3-way merge in the working directory
 
@@ -588,7 +610,7 @@ 
     toolconf = tool, toolpath, binary, symlink
 
     if mergetype == nomerge:
-        r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf)
+        r, deleted = func(repo, mynode, orig, fcd, fco, fca, toolconf, labels)
         return True, r, deleted
 
     if premerge:
diff --git a/mercurial/merge.py b/mercurial/merge.py
--- a/mercurial/merge.py
+++ b/mercurial/merge.py
@@ -1535,11 +1535,13 @@ 
         if '.hgsubstate' in actionbyfile:
             f = '.hgsubstate'
             m, args, msg = actionbyfile[f]
+            prompts = filemerge.partextras(labels)
+            prompts['f'] = f
             if m == 'cd':
                 if repo.ui.promptchoice(
-                    _("local changed %s which remote deleted\n"
+                    _("local%(l)s changed %(f)s which remote%(o)s deleted\n"
                       "use (c)hanged version or (d)elete?"
-                      "$$ &Changed $$ &Delete") % f, 0):
+                      "$$ &Changed $$ &Delete") % prompts, 0):
                     actionbyfile[f] = ('r', None, "prompt delete")
                 elif f in p1:
                     actionbyfile[f] = ('am', None, "prompt keep")
@@ -1549,9 +1551,9 @@ 
                 f1, f2, fa, move, anc = args
                 flags = p2[f2].flags()
                 if repo.ui.promptchoice(
-                    _("remote changed %s which local deleted\n"
+                    _("remote%(o)s changed %(f)s which local%(l)s deleted\n"
                       "use (c)hanged version or leave (d)eleted?"
-                      "$$ &Changed $$ &Deleted") % f, 0) == 0:
+                      "$$ &Changed $$ &Deleted") % prompts, 0) == 0:
                     actionbyfile[f] = ('g', (flags, False), "prompt recreating")
                 else:
                     del actionbyfile[f]
diff --git a/tests/failfilemerge.py b/tests/failfilemerge.py
--- a/tests/failfilemerge.py
+++ b/tests/failfilemerge.py
@@ -9,7 +9,7 @@ 
 )
 
 def failfilemerge(filemergefn,
-        premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
+                  premerge, repo, mynode, orig, fcd, fco, fca, labels=None):
     raise error.Abort("^C")
     return filemergefn(premerge, repo, mynode, orig, fcd, fco, fca, labels)
 
diff --git a/tests/test-copy-move-merge.t b/tests/test-copy-move-merge.t
--- a/tests/test-copy-move-merge.t
+++ b/tests/test-copy-move-merge.t
@@ -85,7 +85,7 @@ 
   > c
   > EOF
   rebasing 2:add3f11052fa "other" (tip)
-  remote changed a which local deleted
+  remote [source] changed a which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
 
   $ cat b
diff --git a/tests/test-largefiles-update.t b/tests/test-largefiles-update.t
--- a/tests/test-largefiles-update.t
+++ b/tests/test-largefiles-update.t
@@ -611,7 +611,7 @@ 
   > EOF
   rebasing 1:72518492caa6 "#1"
   rebasing 4:07d6153b5c04 "#4"
-  local changed .hglf/large1 which remote deleted
+  local [dest] changed .hglf/large1 which remote [source] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? c
 
   $ hg diff -c "tip~1" --nodates .hglf/large1 | grep '^[+-][0-9a-z]'
diff --git a/tests/test-merge-changedelete.t b/tests/test-merge-changedelete.t
--- a/tests/test-merge-changedelete.t
+++ b/tests/test-merge-changedelete.t
@@ -717,9 +717,9 @@ 
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 -y
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -893,9 +893,9 @@ 
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 --config ui.interactive=True --tool :prompt
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -943,9 +943,9 @@ 
   $ echo changed >> file1
   $ hg rm file2
   $ hg update 1 --tool :merge3
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? u
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? u
   1 files updated, 0 files merged, 0 files removed, 2 files unresolved
   use 'hg resolve' to retry unresolved file merges
@@ -999,9 +999,9 @@ 
   (status identical)
   
   === :other -> :prompt ===
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
@@ -1026,9 +1026,9 @@ 
   (status identical)
   
   === :local -> :prompt ===
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
@@ -1043,9 +1043,9 @@ 
   (status identical)
   
   === :fail -> :prompt ===
-  local changed file1 which remote deleted
+  local [working copy] changed file1 which remote [destination] deleted
   use (c)hanged version, (d)elete, or leave (u)nresolved? 
-  remote changed file2 which local deleted
+  remote [destination] changed file2 which local [working copy] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? 
   --- diff of status ---
   (status identical)
diff --git a/tests/test-merge-types.t b/tests/test-merge-types.t
--- a/tests/test-merge-types.t
+++ b/tests/test-merge-types.t
@@ -173,7 +173,7 @@ 
   (couldn't find merge tool hgmerge|tool hgmerge can't handle symlinks) (re)
   picked tool ':prompt' for a (binary False symlink True changedelete False)
   no tool found to merge a
-  keep (l)ocal, take (o)ther, or leave (u)nresolved? u
+  keep (l)ocal [working copy], take (o)ther [destination], or leave (u)nresolved? u
   0 files updated, 0 files merged, 0 files removed, 1 files unresolved
   use 'hg resolve' to retry unresolved file merges
   1 other heads for branch "default"
diff --git a/tests/test-rebase-newancestor.t b/tests/test-rebase-newancestor.t
--- a/tests/test-rebase-newancestor.t
+++ b/tests/test-rebase-newancestor.t
@@ -135,7 +135,7 @@ 
   note: rebase of 1:1d1a643d390e created no changes to commit
   rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
   rebasing 4:4b019212aaf6 "dev: merge default"
-  remote changed f-default which local deleted
+  remote [source] changed f-default which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   rebasing 6:9455ee510502 "dev: merge default"
   saved backup bundle to $TESTTMP/ancestor-merge/.hg/strip-backup/1d1a643d390e-43e9e04b-backup.hg (glob)
@@ -164,7 +164,7 @@ 
   > EOF
   rebasing 2:ec2c14fb2984 "dev: f-dev stuff"
   rebasing 4:4b019212aaf6 "dev: merge default"
-  remote changed f-default which local deleted
+  remote [source] changed f-default which local [dest] deleted
   use (c)hanged version, leave (d)eleted, or leave (u)nresolved? c
   rebasing 6:9455ee510502 "dev: merge default"
   saved backup bundle to $TESTTMP/ancestor-merge-2/.hg/strip-backup/ec2c14fb2984-62d0b222-backup.hg (glob)
diff --git a/tests/test-subrepo-missing.t b/tests/test-subrepo-missing.t
--- a/tests/test-subrepo-missing.t
+++ b/tests/test-subrepo-missing.t
@@ -62,7 +62,7 @@ 
   2 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ rm .hgsubstate
   $ hg up 0
-  remote changed .hgsubstate which local deleted
+  remote [destination] changed .hgsubstate which local [working copy] deleted
   use (c)hanged version or leave (d)eleted? c
   1 files updated, 0 files merged, 0 files removed, 0 files unresolved
   $ hg st