Re: [PATCH stable] tests: use an absolute path to get around '..' being invalid on a dead CWD

2017-01-19 Thread Augie Fackler

> On Jan 19, 2017, at 4:44 PM, Sean Farley  wrote:
> 
> I guess so ... crazy freebsd.

I had another thought on the way home: this might only be a problem in a jail - 
it’s not the first bit of strange behavior we’ve caught with hg when running in 
my buildbot-in-a-jail.

In any event, I think this fixes the last test failure on the BSD builder.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH STABLE] pager: wrap _runcommand() no matter if stdout is redirected

2017-01-19 Thread Augie Fackler
On Thu, Jan 19, 2017 at 11:39:24PM +0900, Yuya Nishihara wrote:
> # HG changeset patch
> # User Yuya Nishihara 
> # Date 1484834492 -32400
> #  Thu Jan 19 23:01:32 2017 +0900
> # Branch stable
> # Node ID f3ca8b7e0e2df7507661adf5957c51e39bc6b5b1
> # Parent  262c2be8ea5a4f36fb7348cb5a940cf78f2dffa9
> pager: wrap _runcommand() no matter if stdout is redirected

Queued for stable. Thanks!

>
> We've made chg utilize the common code path implemented in pager.py (by
> 815e1cefd082 and 493935e0327a), but the chg server does not always start
> with a tty. Because of this, uisetup() of the pager extension could be
> skipped on the chg server.
>
> Kudos given to Sean Farley for dogfooding new chg and spotting this problem.
>
> diff --git a/hgext/pager.py b/hgext/pager.py
> --- a/hgext/pager.py
> +++ b/hgext/pager.py
> @@ -115,9 +115,6 @@ def _runpager(ui, p):
>  pager.wait()
>
>  def uisetup(ui):
> -if '--debugger' in sys.argv or not ui.formatted():
> -return
> -
>  class pagerui(ui.__class__):
>  def _runpager(self, pagercmd):
>  _runpager(self, pagercmd)
> @@ -130,7 +127,7 @@ def uisetup(ui):
>  always = util.parsebool(options['pager'])
>  auto = options['pager'] == 'auto'
>
> -if not p:
> +if not p or '--debugger' in sys.argv or not ui.formatted():
>  pass
>  elif always:
>  usepager = True
> diff --git a/tests/test-chg.t b/tests/test-chg.t
> --- a/tests/test-chg.t
> +++ b/tests/test-chg.t
> @@ -32,6 +32,38 @@ long socket path
>
>$ cd ..
>
> +pager
> +-
> +
> +  $ cat >> fakepager.py < +  > import sys
> +  > for line in sys.stdin:
> +  > sys.stdout.write('paged! %r\n' % line)
> +  > EOF
> +
> +enable pager extension globally, but spawns the master server with no tty:
> +
> +  $ chg init pager
> +  $ cd pager
> +  $ cat >> $HGRCPATH < +  > [extensions]
> +  > pager =
> +  > [pager]
> +  > pager = python $TESTTMP/fakepager.py
> +  > EOF
> +  $ chg version > /dev/null
> +  $ touch foo
> +  $ chg ci -qAm foo
> +
> +pager should be enabled if the attached client has a tty:
> +
> +  $ chg log -l1 -q --config ui.formatted=True
> +  paged! '0:1f7b0de80e11\n'
> +  $ chg log -l1 -q --config ui.formatted=False
> +  0:1f7b0de80e11
> +
> +  $ cd ..
> +
>  server lifecycle
>  
>
> diff --git a/tests/test-pager.t b/tests/test-pager.t
> --- a/tests/test-pager.t
> +++ b/tests/test-pager.t
> @@ -152,6 +152,11 @@ doesn't result in history being paged.
>summary: modify a 9
>
>
> +Pager should not start if stdout is not a tty.
> +
> +  $ hg log -l1 -q --config ui.formatted=False
> +  10:46106edeeb38
> +
>  Pager with color enabled allows colors to come through by default,
>  even though stdout is no longer a tty.
>$ cat >> $HGRCPATH < ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH shelve-ext] shelve: make unshelve not crash when there are missing files (issue4176)

2017-01-19 Thread Augie Fackler
On Thu, Jan 19, 2017 at 09:51:24AM -0800, Kostia Balytskyi wrote:
> # HG changeset patch
> # User Kostia Balytskyi 
> # Date 1484848120 28800
> #  Thu Jan 19 09:48:40 2017 -0800
> # Node ID 2a1e998e369d2c5dc828ba805beceb15459746cd
> # Parent  9f264adbe75bfae8551dc0e6e0fce8d43fc7b43a
> shelve: make unshelve not crash when there are missing files (issue4176)

Queued for stable, thanks.

>
> This patch makes it possible to unshelve while having missing files
> in your repo as long as shelved changes don't touch those missing files.
> It also makes error message better otherwise.
>
> diff --git a/hgext/shelve.py b/hgext/shelve.py
> --- a/hgext/shelve.py
> +++ b/hgext/shelve.py
> @@ -650,7 +650,7 @@ def _commitworkingcopychanges(ui, repo,
>  # contains unknown files that are part of the pending change
>  s = repo.status()
>  addedbefore = frozenset(s.added)
> -if not (s.modified or s.added or s.removed or s.deleted):
> +if not (s.modified or s.added or s.removed):
>  return tmpwctx, addedbefore
>  ui.status(_("temporarily committing pending changes "
>  "(restore with 'hg unshelve --abort')\n"))
> @@ -729,6 +729,17 @@ def _finishunshelve(repo, oldtiprev, tr)
>  repo.unfiltered().changelog.strip(oldtiprev, tr)
>  _aborttransaction(repo)
>
> +def _checkunshelveuntrackedproblems(ui, repo, shelvectx):
> +"""Check potential problems which may result from working
> +copy having untracked changes."""
> +wcdeleted = set(repo.status().deleted)
> +shelvetouched = set(shelvectx.files())
> +intersection = wcdeleted.intersection(shelvetouched)
> +if intersection:
> +m = _("shelved change touches missing files")
> +hint = _("run hg status to see which files are missing")
> +raise error.Abort(m, hint=hint)
> +
>  @command('unshelve',
>   [('a', 'abort', None,
> _('abort an incomplete unshelve operation')),
> @@ -857,7 +868,7 @@ def _dounshelve(ui, repo, *shelved, **op
>   tmpwctx)
>
>  repo, shelvectx = _unshelverestorecommit(ui, repo, basename, 
> oldquiet)
> -
> +_checkunshelveuntrackedproblems(ui, repo, shelvectx)
>  branchtorestore = ''
>  if shelvectx.branch() != shelvectx.p1().branch():
>  branchtorestore = shelvectx.branch()
> diff --git a/tests/test-shelve.t b/tests/test-shelve.t
> --- a/tests/test-shelve.t
> +++ b/tests/test-shelve.t
> @@ -1710,3 +1710,30 @@ Unshelve respects --keep even if user in
>$ hg shelve --list
>default (*s ago)changes to: 1 (glob)
>$ cd ..
> +
> +Unshelving when there are deleted files does not crash (issue4176)
> +  $ hg init unshelve-deleted-file && cd unshelve-deleted-file
> +  $ echo a > a && echo b > b && hg ci -Am ab
> +  adding a
> +  adding b
> +  $ echo aa > a && hg shelve
> +  shelved as default
> +  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  $ rm b
> +  $ hg st
> +  ! b
> +  $ hg unshelve
> +  unshelving change 'default'
> +  $ hg shelve
> +  shelved as default
> +  1 files updated, 0 files merged, 0 files removed, 0 files unresolved
> +  $ rm a && echo b > b
> +  $ hg st
> +  ! a
> +  $ hg unshelve
> +  unshelving change 'default'
> +  abort: shelved change touches missing files
> +  (run hg status to see which files are missing)
> +  [255]
> +  $ hg st
> +  ! a
> ___
> Mercurial-devel mailing list
> Mercurial-devel@mercurial-scm.org
> https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH stable] tests: use an absolute path to get around '..' being invalid on a dead CWD

2017-01-19 Thread Sean Farley
Augie Fackler  writes:

> # HG changeset patch
> # User Augie Fackler 
> # Date 1484861029 18000
> #  Thu Jan 19 16:23:49 2017 -0500
> # Branch stable
> # Node ID 34923f3f218573ec57575f4ae59cb4198cc6a313
> # Parent  b3d2e8cce78c04dbf6f20f0846b6ea8b622983c4
> tests: use an absolute path to get around '..' being invalid on a dead CWD
>
> Only FreeBSD seems to be this picky. Note that this explicit
> absolute-path `cd` exposes a defect in the test, in that we end up
> still inside the cwd-vanish repository, but that's not a regression in
> this change. Since we're in a code freeze, I'm doing the smallest
> thing possible to try and fix bugs on FreeBSD, rather than cleaning up
> the entire problem. I'll follow up with a more complete fix after the
> freeze.

I guess so ... crazy freebsd.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH v9 RFC] scmutil: add a simple key-value file helper

2017-01-19 Thread Sean Farley
Kostia Balytskyi  writes:

> # HG changeset patch
> # User Kostia Balytskyi 
> # Date 1484824655 28800
> #  Thu Jan 19 03:17:35 2017 -0800
> # Node ID 19a449c91ef14e691cf1347748473e0094fedc86
> # Parent  9f264adbe75bfae8551dc0e6e0fce8d43fc7b43a
> scmutil: add a simple key-value file helper
>
> The purpose of the added class is to serve purposes like save files of shelve
> or state files of shelve, rebase and histedit. Keys of these files can be
> alphanumeric and start with letters, while values must not contain newlines.
> Keys which start with an uppercase letter are required, while other keys
> are optional.
>
> In light of Mercurial's reluctancy to use Python's json module, this tries
> to provide a reasonable alternative for a non-nested named data.
> Comparing to current approach of storing state in plain text files, where
> semantic meaning of lines of text is only determined by their oreder,
> simple key-value file allows for reordering lines and thus helps handle
> optional values.
>
> Initial use-case I see for this is obs-shelve's shelve files. Later we
> can possibly migrate state files to this approach.
>
> The test is in a new file beause I did not figure out where to put it
> within existing test suite. If you give me a better idea, I will gladly
> follow it.

Let's hold off until the freeze is over.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 1 of 5] pager: stdout is line buffered by default

2017-01-19 Thread Martin von Zweigbergk via Mercurial-devel
On Thu, Jan 19, 2017 at 11:02 AM, Simon Farnsworth  wrote:
> # HG changeset patch
> # User Simon Farnsworth 
> # Date 1484835774 28800
> #  Thu Jan 19 06:22:54 2017 -0800
> # Node ID 76123ae2e0ccaa58db3d4fc26b75b7251e13ad16
> # Parent  036c37bd3ec189480647ff568cee9e0b43a5bc81
> pager: stdout is line buffered by default

We're in a code freeze and accept patches for the stable branch only,
so please resend this after the freeze is over (~ Feb 3).
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 4 of 5] ui: log time spent blocked on the user

2017-01-19 Thread Sean Farley
Simon Farnsworth  writes:

> # HG changeset patch
> # User Simon Farnsworth 
> # Date 1484842917 28800
> #  Thu Jan 19 08:21:57 2017 -0800
> # Node ID 93221dde2fad942c4f920dab1f346da71ac8033d
> # Parent  e8cd90ea5d3eee923304c64a19c3be9bce50451c
> ui: log time spent blocked on the user
>
> We use a wrapper around Mercurial at Facebook that logs key statistics (like
> elpased time) to our standard performance tooling.
>
> This is less useful than it could be, because we currently can't tell when a
> command is slow because we need to fix Mercurial versus when a command is
> slow because the user isn't interacting quickly.
>
> Teach Mercurial to log the time it spends blocked, so that our tooling can
> pick it up and submit it with the elapsed time - we can then do the math in
> our tooling to see if Mercurial is slow, or if the user simply failed to
> interact.

Augie cut a 4.1-rc yesterday so that means we're in a freeze until 4.1
is out. Though, this series looks good :-) Please resend after Feb. ~1.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 1 of 5] pager: stdout is line buffered by default

2017-01-19 Thread Simon Farnsworth
# HG changeset patch
# User Simon Farnsworth 
# Date 1484835774 28800
#  Thu Jan 19 06:22:54 2017 -0800
# Node ID 76123ae2e0ccaa58db3d4fc26b75b7251e13ad16
# Parent  036c37bd3ec189480647ff568cee9e0b43a5bc81
pager: stdout is line buffered by default

pager only starts when ui.formatted() is true. In normal operation,
when ui.formatted() is true, stdout is line buffered anyway.

The code here doesn't actually do what the commit message in changeset
62c5e937 claims it will do; sys.stdout is not yet pager.stdin when we
reopen, so we affect the original sys.stdout's buffering mode. It's the dup2
that puts pager.stdin's fd into sys.stdout's fd, and that doesn't affect
buffering.

diff --git a/hgext/pager.py b/hgext/pager.py
--- a/hgext/pager.py
+++ b/hgext/pager.py
@@ -87,14 +87,10 @@
  close_fds=util.closefds, stdin=subprocess.PIPE,
  stdout=util.stdout, stderr=util.stderr)
 
-# back up original file objects and descriptors
-olduifout = ui.fout
-oldstdout = util.stdout
+# back up original file descriptors
 stdoutfd = os.dup(util.stdout.fileno())
 stderrfd = os.dup(util.stderr.fileno())
 
-# create new line-buffered stdout so that output can show up immediately
-ui.fout = util.stdout = newstdout = os.fdopen(util.stdout.fileno(), 'wb', 
1)
 os.dup2(pager.stdin.fileno(), util.stdout.fileno())
 if ui._isatty(util.stderr):
 os.dup2(pager.stdin.fileno(), util.stderr.fileno())
@@ -103,15 +99,12 @@
 def killpager():
 if util.safehasattr(signal, "SIGINT"):
 signal.signal(signal.SIGINT, signal.SIG_IGN)
-pager.stdin.close()
-ui.fout = olduifout
-util.stdout = oldstdout
-# close new stdout while it's associated with pager; otherwise stdout
-# fd would be closed when newstdout is deleted
-newstdout.close()
-# restore original fds: stdout is open again
+# restore original fds
 os.dup2(stdoutfd, util.stdout.fileno())
 os.dup2(stderrfd, util.stderr.fileno())
+# close pager's stdin so that it knows we're not going to send any more
+# data
+pager.stdin.close()
 pager.wait()
 
 def uisetup(ui):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 5 of 5] crecord: log blocked time waiting for curses input

2017-01-19 Thread Simon Farnsworth
# HG changeset patch
# User Simon Farnsworth 
# Date 1484850980 28800
#  Thu Jan 19 10:36:20 2017 -0800
# Node ID 9684b31c29f3f17096ead595fef50b687d518b1c
# Parent  93221dde2fad942c4f920dab1f346da71ac8033d
crecord: log blocked time waiting for curses input

We want to know when we're blocked on user input - log it.

diff --git a/mercurial/crecord.py b/mercurial/crecord.py
--- a/mercurial/crecord.py
+++ b/mercurial/crecord.py
@@ -514,6 +514,7 @@
 
 self.ui = ui
 self.opts = {}
+self.elapsedms = 0
 
 self.errorstr = None
 # list of all chunks
@@ -1374,29 +1375,40 @@
 except curses.error:
 pass
 helpwin.refresh()
+if self.ui.logblockedtime:
+helpwin = util.elapsedtimewrapper(helpwin)
 try:
 helpwin.getkey()
 except curses.error:
 pass
+finally:
+if self.ui.logblockedtime:
+self.elapsedms += helpwin.elapsedms
 
 def confirmationwindow(self, windowtext):
 "display an informational window, then wait for and return a keypress."
 
 confirmwin = curses.newwin(self.yscreensize, 0, 0, 0)
+if self.ui.logblockedtime:
+confirmwin = util.elapsedtimewrapper(confirmwin)
 try:
-lines = windowtext.split("\n")
-for line in lines:
-self.printstring(confirmwin, line, pairname="selected")
-except curses.error:
-pass
-self.stdscr.refresh()
-confirmwin.refresh()
-try:
-response = chr(self.stdscr.getch())
-except ValueError:
-response = None
+try:
+lines = windowtext.split("\n")
+for line in lines:
+self.printstring(confirmwin, line, pairname="selected")
+except curses.error:
+pass
+self.stdscr.refresh()
+confirmwin.refresh()
+try:
+response = chr(self.stdscr.getch())
+except ValueError:
+response = None
 
-return response
+return response
+finally:
+if self.ui.logblockedtime:
+self.elapsedms += confirmwin.elapsedms
 
 def reviewcommit(self):
 """ask for 'y' to be pressed to confirm selected. return True if
@@ -1612,6 +1624,8 @@
 origsigwinchhandler = signal.signal(signal.SIGWINCH,
 self.sigwinchhandler)
 self.stdscr = stdscr
+if self.ui.logblockedtime:
+self.stdscr = util.elapsedtimewrapper(self.stdscr)
 # error during initialization, cannot be printed in the curses
 # interface, it should be printed by the calling code
 self.initerr = None
@@ -1630,6 +1644,8 @@
 self.initcolorpair(curses.COLOR_WHITE, curses.COLOR_BLUE, 
name="legend")
 # newwin([height, width,] begin_y, begin_x)
 self.statuswin = curses.newwin(self.numstatuslines, 0, 0, 0)
+if self.ui.logblockedtime:
+self.statuswin = util.elapsedtimewrapper(self.statuswin)
 self.statuswin.keypad(1) # interpret arrow-key, etc. esc sequences
 
 # figure out how much space to allocate for the chunk-pad which is
@@ -1650,15 +1666,23 @@
 self.selecteditemendline = self.getnumlinesdisplayed(
 self.currentselecteditem, recursechildren=False)
 
-while True:
-self.updatescreen()
-try:
-keypressed = self.statuswin.getkey()
-if self.errorstr is not None:
-self.errorstr = None
-continue
-except curses.error:
-keypressed = "foobar"
-if self.handlekeypressed(keypressed):
-break
+try:
+while True:
+self.updatescreen()
+try:
+keypressed = self.statuswin.getkey()
+if self.errorstr is not None:
+self.errorstr = None
+continue
+except curses.error:
+keypressed = "foobar"
+if self.handlekeypressed(keypressed):
+break
+finally:
+if self.ui.logblockedtime:
+self.elapsedms += self.stdscr.elapsedms
+self.elapsedms += self.statuswin.elapsedms
+self.ui.log('uiblocked',
+'crecord blocked for %0.1f ms', self.elapsed_ms,
+crecord_blocked=self.elapsedms)
 signal.signal(signal.SIGWINCH, origsigwinchhandler)
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[Bug 5469] New: Add arguments to narrow the results of hg resolve --list

2017-01-19 Thread mercurial-bugs
https://bz.mercurial-scm.org/show_bug.cgi?id=5469

Bug ID: 5469
   Summary: Add arguments to narrow the results of hg resolve
--list
   Product: Mercurial
   Version: stable branch
  Hardware: PC
OS: Mac OS
Status: UNCONFIRMED
  Severity: feature
  Priority: wish
 Component: Mercurial
  Assignee: bugzi...@mercurial-scm.org
  Reporter: joshg...@alum.mit.edu
CC: mercurial-de...@selenic.com

Currently the only way I know to get the list of resolved or unresolved files
during a merge is to use grep and sed on the output of "hg resolve --list". 
Could we add arguments that list only the resolved or only the unresolved
files?

It might make sense for either option to imply --no-status.

-- 
You are receiving this mail because:
You are on the CC list for the bug.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH] ui: rename tmpdir parameter to more specific repopath

2017-01-19 Thread Sean Farley
Yuya Nishihara  writes:

> On Wed, 18 Jan 2017 18:38:54 -0800, Sean Farley wrote:
>> # HG changeset patch
>> # User Sean Farley 
>> # Date 1484792751 28800
>> #  Wed Jan 18 18:25:51 2017 -0800
>> # Branch stable
>> # Node ID 41d220e2bed95664c335f6a7ef70b8ce06dca86c
>> # Parent  94af7d0c812fe7d3a5651191685ca43e1a331814
>> ui: rename tmpdir parameter to more specific repopath
>> 
>> This was requested by Augie and I agree that repopath is more
>> descriptive.
>> 
>> diff --git a/hgext/histedit.py b/hgext/histedit.py
>> index ae860d8..3e11fff 100644
>> --- a/hgext/histedit.py
>> +++ b/hgext/histedit.py
>> @@ -1333,11 +1333,11 @@ def ruleeditor(repo, ui, actions, editco
>>  
>>  rules = '\n'.join([act.torule() for act in actions])
>>  rules += '\n\n'
>>  rules += editcomment
>>  rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
>> -tmpdir=repo.path)
>> +repopath=repo.path)
>
> repo.path could be set to ui at localrepository.__init__(), if we _always_
> want to switch the tempdir. Just an idea, which is obviously out of stable
> scope.

That's not a bad idea! I'll think about that after the freeze.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


mercurial@30844: 2 new changesets (2 on stable)

2017-01-19 Thread Mercurial Commits
2 new changesets (2 on stable) in mercurial:

https://www.mercurial-scm.org/repo/hg/rev/2fb3ae89e4e1
changeset:   30843:2fb3ae89e4e1
branch:  stable
user:Augie Fackler 
date:Wed Jan 18 23:34:35 2017 -0500
summary: contrib: fix check-commit to not reject commits from `hg sign` and 
`hg tag`

https://www.mercurial-scm.org/repo/hg/rev/b3d2e8cce78c
changeset:   30844:b3d2e8cce78c
branch:  stable
bookmark:@
tag: tip
user:Augie Fackler 
date:Wed Jan 18 23:43:41 2017 -0500
summary: tests: work around FreeBSD's unzip having slightly different output

-- 
Repository URL: https://www.mercurial-scm.org/repo/hg
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 02 of 10 shelve-ext v2] shelve: rename stripnodes to nodestoprune

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID 155f97b77a4866075fa709daa3bef6dac9108e81
# Parent  d904df83e9ead56f65104e10d765c0157d647401
shelve: rename stripnodes to nodestoprune

Since we are introducing obs-based shelve, we are no longer
stripping temporary nodes, we are obsoleting them. Therefore
it looks like stipnodes would be a misleading name, while
prune has a connotaion of "strip but with obsolescense", so
nodestoprune seems like a good rename.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -186,7 +186,7 @@ class shelvedstate(object):
 wctx = nodemod.bin(fp.readline().strip())
 pendingctx = nodemod.bin(fp.readline().strip())
 parents = [nodemod.bin(h) for h in fp.readline().split()]
-stripnodes = [nodemod.bin(h) for h in fp.readline().split()]
+nodestoprune = [nodemod.bin(h) for h in fp.readline().split()]
 branchtorestore = fp.readline().strip()
 keep = fp.readline().strip() == cls._keep
 except (ValueError, TypeError) as err:
@@ -200,7 +200,7 @@ class shelvedstate(object):
 obj.wctx = repo[wctx]
 obj.pendingctx = repo[pendingctx]
 obj.parents = parents
-obj.stripnodes = stripnodes
+obj.nodestoprune = nodestoprune
 obj.branchtorestore = branchtorestore
 obj.keep = keep
 except error.RepoLookupError as err:
@@ -209,7 +209,7 @@ class shelvedstate(object):
 return obj
 
 @classmethod
-def save(cls, repo, name, originalwctx, pendingctx, stripnodes,
+def save(cls, repo, name, originalwctx, pendingctx, nodestoprune,
  branchtorestore, keep=False):
 fp = repo.vfs(cls._filename, 'wb')
 fp.write('%i\n' % cls._version)
@@ -219,7 +219,7 @@ class shelvedstate(object):
 fp.write('%s\n' %
  ' '.join([nodemod.hex(p) for p in repo.dirstate.parents()]))
 fp.write('%s\n' %
- ' '.join([nodemod.hex(n) for n in stripnodes]))
+ ' '.join([nodemod.hex(n) for n in nodestoprune]))
 fp.write('%s\n' % branchtorestore)
 fp.write('%s\n' % (cls._keep if keep else cls._nokeep))
 fp.close()
@@ -570,7 +570,7 @@ def unshelveabort(ui, repo, state, opts)
 raise
 
 mergefiles(ui, repo, state.wctx, state.pendingctx)
-repair.strip(ui, repo, state.stripnodes, backup=False,
+repair.strip(ui, repo, state.nodestoprune, backup=False,
  topic='shelve')
 finally:
 shelvedstate.clear(repo)
@@ -643,12 +643,12 @@ def unshelvecontinue(ui, repo, state, op
 shelvectx = state.pendingctx
 else:
 # only strip the shelvectx if the rebase produced it
-state.stripnodes.append(shelvectx.node())
+state.nodestoprune.append(shelvectx.node())
 
 mergefiles(ui, repo, state.wctx, shelvectx)
 restorebranch(ui, repo, state.branchtorestore)
 
-repair.strip(ui, repo, state.stripnodes, backup=False, topic='shelve')
+repair.strip(ui, repo, state.nodestoprune, backup=False, 
topic='shelve')
 shelvedstate.clear(repo)
 unshelvecleanup(ui, repo, state.name, opts)
 ui.status(_("unshelve of '%s' complete\n") % state.name)
@@ -700,9 +700,9 @@ def _rebaserestoredcommit(ui, repo, opts
 except error.InterventionRequired:
 tr.close()
 
-stripnodes = [repo.changelog.node(rev)
-  for rev in xrange(oldtiprev, len(repo))]
-shelvedstate.save(repo, basename, pctx, tmpwctx, stripnodes,
+nodestoprune = [repo.changelog.node(rev)
+for rev in xrange(oldtiprev, len(repo))]
+shelvedstate.save(repo, basename, pctx, tmpwctx, nodestoprune,
   branchtorestore, opts.get('keep'))
 
 util.rename(repo.join('rebasestate'),
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 03 of 10 shelve-ext v2] shelve: move node-pruning functionality to be member of shelvedstate

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID 249273f6db8bf0fdfbbf36bcbd9e3553e5715212
# Parent  155f97b77a4866075fa709daa3bef6dac9108e81
shelve: move node-pruning functionality to be member of shelvedstate

Node-pruning can be node stripping or marker creation, depending on
whether shelve is traditional or obs-based. Thus it makes sense to
move it to a separate function. Also, since we already have
shelvedstate object and this functionality operates on that object,
it makes sense to make it a method.

Having shelvedstate object contain repo and ui as members allows for
calling 'state.prunenodes()' instead of 'state.prunenodes(repo, ui)'
which is better IMO.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -173,8 +173,12 @@ class shelvedstate(object):
 _keep = 'keep'
 _nokeep = 'nokeep'
 
+def __init__(self, ui, repo):
+self.ui = ui
+self.repo = repo
+
 @classmethod
-def load(cls, repo):
+def load(cls, ui, repo):
 fp = repo.vfs(cls._filename)
 try:
 version = int(fp.readline().strip())
@@ -195,7 +199,7 @@ class shelvedstate(object):
 fp.close()
 
 try:
-obj = cls()
+obj = cls(ui, repo)
 obj.name = name
 obj.wctx = repo[wctx]
 obj.pendingctx = repo[pendingctx]
@@ -228,6 +232,11 @@ class shelvedstate(object):
 def clear(cls, repo):
 util.unlinkpath(repo.join(cls._filename), ignoremissing=True)
 
+def prunenodes(self):
+"""Cleanup temporary nodes from the repo"""
+repair.strip(self.ui, self.repo, self.nodestoprune, backup=False,
+ topic='shelve')
+
 def cleanupoldbackups(repo):
 vfs = scmutil.vfs(repo.join(backupdir))
 maxbackups = repo.ui.configint('shelve', 'maxbackups', 10)
@@ -570,8 +579,7 @@ def unshelveabort(ui, repo, state, opts)
 raise
 
 mergefiles(ui, repo, state.wctx, state.pendingctx)
-repair.strip(ui, repo, state.nodestoprune, backup=False,
- topic='shelve')
+state.prunenodes()
 finally:
 shelvedstate.clear(repo)
 ui.warn(_("unshelve of '%s' aborted\n") % state.name)
@@ -648,7 +656,7 @@ def unshelvecontinue(ui, repo, state, op
 mergefiles(ui, repo, state.wctx, shelvectx)
 restorebranch(ui, repo, state.branchtorestore)
 
-repair.strip(ui, repo, state.nodestoprune, backup=False, 
topic='shelve')
+state.prunenodes()
 shelvedstate.clear(repo)
 unshelvecleanup(ui, repo, state.name, opts)
 ui.status(_("unshelve of '%s' complete\n") % state.name)
@@ -804,7 +812,7 @@ def _dounshelve(ui, repo, *shelved, **op
 ui.warn(_('tool option will be ignored\n'))
 
 try:
-state = shelvedstate.load(repo)
+state = shelvedstate.load(ui, repo)
 if opts.get('keep') is None:
 opts['keep'] = state.keep
 except IOError as err:
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 06 of 10 shelve-ext v2] shelve: add shelve type saving and loading

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID 149fc6d767ce3502528b43b0e209eb411dd6e842
# Parent  44425c4e86b2589184e23bed798999c15788b54b
shelve: add shelve type saving and loading

We need shelve type to be stored in .hg/shelvedstate in order
to be able to run abort or continue action properly. If the shelve
is obsbased, those actions should create markes, if it is traditional,
the actions should strip commits.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -185,6 +185,8 @@ class shelvedstate(object):
 _filename = 'shelvedstate'
 _keep = 'keep'
 _nokeep = 'nokeep'
+_obsbased = 'obsbased'
+_traditional = 'traditional'
 
 def __init__(self, ui, repo):
 self.ui = ui
@@ -206,6 +208,7 @@ class shelvedstate(object):
 nodestoprune = [nodemod.bin(h) for h in fp.readline().split()]
 branchtorestore = fp.readline().strip()
 keep = fp.readline().strip() == cls._keep
+obsshelve = fp.readline().strip() == cls._obsbased
 except (ValueError, TypeError) as err:
 raise error.CorruptedState(str(err))
 finally:
@@ -220,6 +223,7 @@ class shelvedstate(object):
 obj.nodestoprune = nodestoprune
 obj.branchtorestore = branchtorestore
 obj.keep = keep
+obj.obsshelve = obsshelve
 except error.RepoLookupError as err:
 raise error.CorruptedState(str(err))
 
@@ -227,7 +231,7 @@ class shelvedstate(object):
 
 @classmethod
 def save(cls, repo, name, originalwctx, pendingctx, nodestoprune,
- branchtorestore, keep=False):
+ branchtorestore, keep=False, obsshelve=False):
 fp = repo.vfs(cls._filename, 'wb')
 fp.write('%i\n' % cls._version)
 fp.write('%s\n' % name)
@@ -239,6 +243,7 @@ class shelvedstate(object):
  ' '.join([nodemod.hex(n) for n in nodestoprune]))
 fp.write('%s\n' % branchtorestore)
 fp.write('%s\n' % (cls._keep if keep else cls._nokeep))
+fp.write('%s\n' % (cls._obsbased if obsshelve else cls._traditional))
 fp.close()
 
 @classmethod
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 05 of 10 shelve-ext v2] shelve: add obs-based shelve functionality

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID 44425c4e86b2589184e23bed798999c15788b54b
# Parent  ceb709491816f84789b77a18bfcab15eaa9860fe
shelve: add obs-based shelve functionality

Obsolescense-based shelve works in a following way:
1. In order to shelve some changes, it creates a commit, records its
node into a .oshelve file and prunes created commit.
2. In order to finish a shelve operation, transaction is just
closed and not aborted.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -373,10 +373,15 @@ def _nothingtoshelvemessaging(ui, repo, 
 else:
 ui.status(_("nothing changed\n"))
 
-def _shelvecreatedcommit(repo, node, name):
-bases = list(mutableancestors(repo[node]))
-shelvedfile(repo, name, 'hg').writebundle(bases, node)
-cmdutil.export(repo, [node],
+def _shelvecreatedcommit(ui, repo, node, name, tr):
+if isobsshelve(repo, ui):
+shelvedfile(repo, name, 'oshelve').writeobsshelveinfo({
+'node': nodemod.hex(node)
+})
+else:
+bases = list(mutableancestors(repo[node]))
+shelvedfile(repo, name, 'hg').writebundle(bases, node)
+cmdutil.export(repo.unfiltered(), [node],
fp=shelvedfile(repo, name, patchextension).opener('wb'),
opts=mdiff.diffopts(git=True))
 
@@ -387,8 +392,13 @@ def _includeunknownfiles(repo, pats, opt
 extra['shelve_unknown'] = '\0'.join(s.unknown)
 repo[None].add(s.unknown)
 
-def _finishshelve(repo):
-_aborttransaction(repo)
+def _finishshelve(ui, repo, tr, node):
+if isobsshelve(repo, ui):
+obsolete.createmarkers(repo, [(repo.unfiltered()[node], ())])
+tr.close()
+tr.release()
+else:
+_aborttransaction(repo)
 
 def _docreatecmd(ui, repo, pats, opts):
 wctx = repo[None]
@@ -410,9 +420,12 @@ def _docreatecmd(ui, repo, pats, opts):
 try:
 lock = repo.lock()
 
-# use an uncommitted transaction to generate the bundle to avoid
-# pull races. ensure we don't print the abort message to stderr.
-tr = repo.transaction('commit', report=lambda x: None)
+# depending on whether shelve is traditional or
+# obsolescense-based, we either abort or commit this
+# transaction in the end. If we abort it, we don't
+# want to print anything to stderr
+report = None if isobsshelve(repo, ui) else (lambda x: None)
+tr = repo.transaction('commit', report=report)
 
 interactive = opts.get('interactive', False)
 includeunknown = (opts.get('unknown', False) and
@@ -438,16 +451,19 @@ def _docreatecmd(ui, repo, pats, opts):
 _nothingtoshelvemessaging(ui, repo, pats, opts)
 return 1
 
-_shelvecreatedcommit(repo, node, name)
+_shelvecreatedcommit(ui, repo, node, name, tr)
 
 if ui.formatted():
 desc = util.ellipsis(desc, ui.termwidth())
 ui.status(_('shelved as %s\n') % name)
-hg.update(repo, parent.node())
+# current wc parent may be already obsolete becuase
+# it might have been created previously and shelve just
+# reuses it
+hg.update(repo.unfiltered(), parent.node())
 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
 repo.dirstate.setbranch(origbranch)
 
-_finishshelve(repo)
+_finishshelve(ui, repo, tr, node)
 finally:
 lockmod.release(tr, lock)
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 09 of 10 shelve-ext v2] shelve: add logic to preserve active bookmarks

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID 088c9191d662d5c0003310119c51540926a815f7
# Parent  94a237a046059ef246aacb2c3ad809c9f0bdbe70
shelve: add logic to preserve active bookmarks

This adds an explicit active-bookmark-handling logic
to *both* traditional and obs-based shelve. Although it
is possible to only add it to obs-based, I think it would
be ugly and I see no harm in explicitly handling bookmarks
in addition to reliance on trasnactions.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -29,6 +29,7 @@ import time
 
 from mercurial.i18n import _
 from mercurial import (
+bookmarks,
 bundle2,
 bundlerepo,
 changegroup,
@@ -188,6 +189,8 @@ class shelvedstate(object):
 _nokeep = 'nokeep'
 _obsbased = 'obsbased'
 _traditional = 'traditional'
+# colon is essential to differentiate from a real bookmark name
+_noactivebook = ':no-active-bookmark'
 
 def __init__(self, ui, repo):
 self.ui = ui
@@ -210,6 +213,7 @@ class shelvedstate(object):
 branchtorestore = fp.readline().strip()
 keep = fp.readline().strip() == cls._keep
 obsshelve = fp.readline().strip() == cls._obsbased
+activebook = fp.readline().strip()
 except (ValueError, TypeError) as err:
 raise error.CorruptedState(str(err))
 finally:
@@ -225,6 +229,9 @@ class shelvedstate(object):
 obj.branchtorestore = branchtorestore
 obj.keep = keep
 obj.obsshelve = obsshelve
+obj.activebookmark = ''
+if activebook != cls._noactivebook:
+obj.activebookmark = activebook
 except error.RepoLookupError as err:
 raise error.CorruptedState(str(err))
 
@@ -232,7 +239,7 @@ class shelvedstate(object):
 
 @classmethod
 def save(cls, repo, name, originalwctx, pendingctx, nodestoprune,
- branchtorestore, keep=False, obsshelve=False):
+ branchtorestore, keep=False, obsshelve=False, activebook=''):
 fp = repo.vfs(cls._filename, 'wb')
 fp.write('%i\n' % cls._version)
 fp.write('%s\n' % name)
@@ -245,6 +252,7 @@ class shelvedstate(object):
 fp.write('%s\n' % branchtorestore)
 fp.write('%s\n' % (cls._keep if keep else cls._nokeep))
 fp.write('%s\n' % (cls._obsbased if obsshelve else cls._traditional))
+fp.write('%s\n' % (activebook or cls._noactivebook))
 fp.close()
 
 @classmethod
@@ -283,6 +291,16 @@ def cleanupoldbackups(repo):
 if err.errno != errno.ENOENT:
 raise
 
+def _backupactivebookmark(repo):
+activebookmark = repo._activebookmark
+if activebookmark:
+bookmarks.deactivate(repo)
+return activebookmark
+
+def _restoreactivebookmark(repo, mark):
+if mark:
+bookmarks.activate(repo, mark)
+
 def _aborttransaction(repo):
 '''Abort current transaction for shelve/unshelve, but keep dirstate
 '''
@@ -402,7 +420,9 @@ def _includeunknownfiles(repo, pats, opt
 extra['shelve_unknown'] = '\0'.join(s.unknown)
 repo[None].add(s.unknown)
 
-def _finishshelve(ui, repo, tr, node):
+def _finishshelve(ui, repo, tr, node, activebookmark):
+if activebookmark:
+bookmarks.activate(repo, activebookmark)
 if isobsshelve(repo, ui):
 obsolete.createmarkers(repo, [(repo.unfiltered()[node], ())])
 tr.close()
@@ -426,7 +446,7 @@ def _docreatecmd(ui, repo, pats, opts):
 if not opts.get('message'):
 opts['message'] = desc
 
-lock = tr = None
+lock = tr = activebookmark = None
 try:
 lock = repo.lock()
 
@@ -442,6 +462,7 @@ def _docreatecmd(ui, repo, pats, opts):
   not opts.get('addremove', False))
 
 name = getshelvename(repo, parent, opts)
+activebookmark = _backupactivebookmark(repo)
 extra = {}
 if includeunknown:
 _includeunknownfiles(repo, pats, opts, extra)
@@ -456,7 +477,8 @@ def _docreatecmd(ui, repo, pats, opts):
 node = cmdutil.commit(ui, repo, commitfunc, pats, opts)
 else:
 node = cmdutil.dorecord(ui, repo, commitfunc, None,
-False, cmdutil.recordfilter, *pats, **opts)
+False, cmdutil.recordfilter, *pats,
+**opts)
 if not node:
 _nothingtoshelvemessaging(ui, repo, pats, opts)
 return 1
@@ -473,8 +495,9 @@ def _docreatecmd(ui, repo, pats, opts):
 if origbranch != repo['.'].branch() and not _isbareshelve(pats, opts):
 repo.dirstate.setbranch(origbranch)
 
-_finishshelve(ui, repo, tr, node)
+_finishshelve(ui, repo, tr, node, activebookmark)
 finally:
+_restoreactivebookmark(repo, activebookmark)
 

[PATCH 08 of 10 shelve-ext v2] shelve: add obs-based unshelve functionality

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484835394 28800
#  Thu Jan 19 06:16:34 2017 -0800
# Node ID 94a237a046059ef246aacb2c3ad809c9f0bdbe70
# Parent  abdf9565fdce15604ea4abf013cb7c98a11f70ca
shelve: add obs-based unshelve functionality

Obsolescense-based unshelve works as follows:
1. Instead of stripping temporary nodes, markers are created to
obsolete them.
2. Restoring commit is just finding it in an unfiltered repo.
3. '--keep' is only passed to rebase on traditional unshelves
(and thus traditional rebases), becuase we want markers to be
created fro obsolete-based rebases.
4. 'hg unshelve' uses unfiltered repo to perform rebases
because we want rebase to be able to create markers between original
and new commits. 'rebaseskipobsolete' is disabled to make rebase not
skip the commit altogether.

I have a test for the case when shelve node has been stripped before
unshelve call, that test is together with ~30 commits I was talking
about in patch 1.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -25,6 +25,7 @@ from __future__ import absolute_import
 import collections
 import errno
 import itertools
+import time
 
 from mercurial.i18n import _
 from mercurial import (
@@ -252,8 +253,13 @@ class shelvedstate(object):
 
 def prunenodes(self):
 """Cleanup temporary nodes from the repo"""
-repair.strip(self.ui, self.repo, self.nodestoprune, backup=False,
- topic='shelve')
+if self.obsshelve:
+unfi = self.repo.unfiltered()
+relations = [(unfi[n], ()) for n in self.nodestoprune]
+obsolete.createmarkers(self.repo, relations)
+else:
+repair.strip(self.ui, self.repo, self.nodestoprune, backup=False,
+ topic='shelve')
 
 def cleanupoldbackups(repo):
 vfs = scmutil.vfs(repo.join(backupdir))
@@ -666,9 +672,14 @@ def unshelvecontinue(ui, repo, state, op
 util.rename(repo.join('unshelverebasestate'),
 repo.join('rebasestate'))
 try:
-rebase.rebase(ui, repo, **{
-'continue' : True
-})
+# if shelve is obs-based, we want rebase to be able
+# to create markers to already-obsoleted commits
+_repo = repo.unfiltered() if state.obsshelve else repo
+with ui.configoverride({('experimental', 'rebaseskipobsolete'):
+'off'}, 'unshelve'):
+rebase.rebase(ui, _repo, **{
+'continue' : True,
+})
 except Exception:
 util.rename(repo.join('rebasestate'),
 repo.join('unshelverebasestate'))
@@ -708,30 +719,58 @@ def _commitworkingcopychanges(ui, repo, 
 with ui.configoverride({('ui', 'quiet'): True}):
 node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
 tmpwctx = repo[node]
+ui.debug("temporary working copy commit: %s:%s\n" %
+ (tmpwctx.rev(), nodemod.short(node)))
 return tmpwctx, addedbefore
 
-def _unshelverestorecommit(ui, repo, basename):
+def _unshelverestorecommit(ui, repo, basename, obsshelve):
 """Recreate commit in the repository during the unshelve"""
 with ui.configoverride({('ui', 'quiet'): True}):
-shelvedfile(repo, basename, 'hg').applybundle()
-shelvectx = repo['tip']
+if obsshelve:
+md = shelvedfile(repo, basename, 'oshelve').readobsshelveinfo()
+shelvenode = nodemod.bin(md['node'])
+repo = repo.unfiltered()
+try:
+shelvectx = repo[shelvenode]
+except error.RepoLookupError:
+m = _("shelved node %s not found in repo")
+raise error.Abort(m % md['node'])
+else:
+shelvedfile(repo, basename, 'hg').applybundle()
+shelvectx = repo['tip']
 return repo, shelvectx
 
 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
-  tmpwctx, shelvectx, branchtorestore):
+  tmpwctx, shelvectx, branchtorestore, obsshelve):
 """Rebase restored commit from its original location to a destination"""
 # If the shelve is not immediately on top of the commit
 # we'll be merging with, rebase it to be on top.
 if tmpwctx.node() == shelvectx.parents()[0].node():
+# shelvectx is immediately on top of the tmpwctx
 return shelvectx
 
+# we need a new commit extra every time we perform a rebase to ensure
+# that "nothing to rebase" does not happen with obs-based shelve
+# "nothing to rebase" means that tip does not point to a "successor"
+# commit after a rebase and we have no way to learn which commit
+# should be a "shelvectx". this is a dirty hack until we implement
+# some way to learn the results of rebase operation, other than
+# text output and 

[PATCH 10 of 10 shelve-ext v2] shelve: disable inhibit for shelving period

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484838335 28800
#  Thu Jan 19 07:05:35 2017 -0800
# Node ID 6a7e7659886d35dfdc4d7d5efb3ef8479ae35367
# Parent  088c9191d662d5c0003310119c51540926a815f7
shelve: disable inhibit for shelving period

While shelving, we're creating a new commit and updating to it.
If inhibit is enabled, it will try to add this commit to an
inhibition set, which is not necessary (we're creating a marker
right after we perform this operation). Thus, disabling inhibit
speeds things up a bit.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -447,8 +447,12 @@ def _docreatecmd(ui, repo, pats, opts):
 opts['message'] = desc
 
 lock = tr = activebookmark = None
+_obsinhibit = _notset = object()
 try:
 lock = repo.lock()
+if util.safehasattr(repo, '_obsinhibit'):
+_obsinhibit = getattr(repo, '_obsinhibit')
+del repo._obsinhibit
 
 # depending on whether shelve is traditional or
 # obsolescense-based, we either abort or commit this
@@ -498,6 +502,8 @@ def _docreatecmd(ui, repo, pats, opts):
 _finishshelve(ui, repo, tr, node, activebookmark)
 finally:
 _restoreactivebookmark(repo, activebookmark)
+if _obsinhibit is not _notset:
+repo._obsinhibit = _obsinhibit
 lockmod.release(tr, lock)
 
 def _isbareshelve(pats, opts):
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 07 of 10 shelve-ext v2] shelve: migrate config overrides to ui.configoverride

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID abdf9565fdce15604ea4abf013cb7c98a11f70ca
# Parent  149fc6d767ce3502528b43b0e209eb411dd6e842
shelve: migrate config overrides to ui.configoverride

This patch also makes ui.quiet manipulations much more explicit
and readable, addressing previous Yuya's concerns.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -349,17 +349,16 @@ def getcommitfunc(extra, interactive, ed
 hasmq = util.safehasattr(repo, 'mq')
 if hasmq:
 saved, repo.mq.checkapplied = repo.mq.checkapplied, False
-backup = repo.ui.backupconfig('phases', 'new-commit')
 try:
-repo.ui.setconfig('phases', 'new-commit', phases.secret)
-editor_ = False
-if editor:
-editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
-  **opts)
-return repo.commit(message, shelveuser, opts.get('date'), match,
-   editor=editor_, extra=extra)
+overrides = {('phases', 'new-commit'): phases.secret}
+with repo.ui.configoverride(overrides):
+editor_ = False
+if editor:
+editor_ = cmdutil.getcommiteditor(editform='shelve.shelve',
+  **opts)
+return repo.commit(message, shelveuser, opts.get('date'),
+   match, editor=editor_, extra=extra)
 finally:
-repo.ui.restoreconfig(backup)
 if hasmq:
 repo.mq.checkapplied = saved
 
@@ -621,9 +620,7 @@ def unshelveabort(ui, repo, state, opts)
 def mergefiles(ui, repo, wctx, shelvectx):
 """updates to wctx and merges the changes from shelvectx into the
 dirstate."""
-oldquiet = ui.quiet
-try:
-ui.quiet = True
+with ui.configoverride({('ui', 'quiet'): True}):
 hg.update(repo, wctx.node())
 files = []
 files.extend(shelvectx.files())
@@ -638,8 +635,6 @@ def mergefiles(ui, repo, wctx, shelvectx
*pathtofiles(repo, files),
**{'no_backup': True})
 ui.popbuffer()
-finally:
-ui.quiet = oldquiet
 
 def restorebranch(ui, repo, branchtorestore):
 if branchtorestore and branchtorestore != repo.dirstate.branch():
@@ -710,17 +705,16 @@ def _commitworkingcopychanges(ui, repo, 
 tempopts = {}
 tempopts['message'] = "pending changes temporary commit"
 tempopts['date'] = opts.get('date')
-ui.quiet = True
-node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
+with ui.configoverride({('ui', 'quiet'): True}):
+node = cmdutil.commit(ui, repo, commitfunc, [], tempopts)
 tmpwctx = repo[node]
 return tmpwctx, addedbefore
 
-def _unshelverestorecommit(ui, repo, basename, oldquiet):
+def _unshelverestorecommit(ui, repo, basename):
 """Recreate commit in the repository during the unshelve"""
-ui.quiet = True
-shelvedfile(repo, basename, 'hg').applybundle()
-shelvectx = repo['tip']
-ui.quiet = oldquiet
+with ui.configoverride({('ui', 'quiet'): True}):
+shelvedfile(repo, basename, 'hg').applybundle()
+shelvectx = repo['tip']
 return repo, shelvectx
 
 def _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev, basename, pctx,
@@ -886,13 +880,9 @@ def _dounshelve(ui, repo, *shelved, **op
 if not shelvedfile(repo, basename, patchextension).exists():
 raise error.Abort(_("shelved change '%s' not found") % basename)
 
-oldquiet = ui.quiet
 lock = tr = None
-forcemerge = ui.backupconfig('ui', 'forcemerge')
 try:
-ui.setconfig('ui', 'forcemerge', opts.get('tool', ''), 'unshelve')
 lock = repo.lock()
-
 tr = repo.transaction('unshelve', report=lambda x: None)
 oldtiprev = len(repo)
 
@@ -907,16 +897,18 @@ def _dounshelve(ui, repo, *shelved, **op
 tmpwctx, addedbefore = _commitworkingcopychanges(ui, repo, opts,
  tmpwctx)
 
-repo, shelvectx = _unshelverestorecommit(ui, repo, basename, oldquiet)
+repo, shelvectx = _unshelverestorecommit(ui, repo, basename)
 
 branchtorestore = ''
 if shelvectx.branch() != shelvectx.p1().branch():
 branchtorestore = shelvectx.branch()
 
-shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,
-  basename, pctx, tmpwctx, shelvectx,
-  branchtorestore)
-mergefiles(ui, repo, pctx, shelvectx)
+with ui.configoverride({('ui', 'forcemerge'): opts.get('tool', '')},
+   'unshelve'):
+shelvectx = _rebaserestoredcommit(ui, repo, opts, tr, oldtiprev,

[PATCH 04 of 10 shelve-ext v2] shelve: add a function to check whether obs-based shelve is enabled

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484740179 28800
#  Wed Jan 18 03:49:39 2017 -0800
# Node ID ceb709491816f84789b77a18bfcab15eaa9860fe
# Parent  249273f6db8bf0fdfbbf36bcbd9e3553e5715212
shelve: add a function to check whether obs-based shelve is enabled

A central place to check whether code should use traditional or
obsolescense-based shelve behavior.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -40,6 +40,7 @@ from mercurial import (
 mdiff,
 merge,
 node as nodemod,
+obsolete,
 patch,
 phases,
 repair,
@@ -70,6 +71,18 @@ patchextension = 'patch'
 # generic user for all shelve operations
 shelveuser = 'shelve@localhost'
 
+def isobsshelve(repo, ui):
+"""Check whether obsolescense-based shelve is enabled"""
+obsshelve = ui.configbool('experimental', 'obsshelve')
+if not obsshelve:
+return False
+if not obsolete.isenabled(repo, obsolete.createmarkersopt):
+w = _('ignoring experimental.obsshelve because createmarkers option '
+  'is disabled')
+ui.warn(w)
+return False
+return True
+
 class obsshelvefile(scmutil.simplekeyvaluefile):
 KEYS = [('node', True)]
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH 01 of 10 shelve-ext v2] shelve: add an ability to write key-val data to a new type of shelve files

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484835841 28800
#  Thu Jan 19 06:24:01 2017 -0800
# Node ID d904df83e9ead56f65104e10d765c0157d647401
# Parent  19a449c91ef14e691cf1347748473e0094fedc86
shelve: add an ability to write key-val data to a new type of shelve files

Obsolescense-based shelve only needs metadata stored in .hg/shelved
and if feels that this metadata should be stored in a
simplekeyvaluefile format for potential extensibility purposes.
I want to avoid storing it in an unstructured text file where
order of lines determines their semantical meanings (as now
happens in .hg/shelvedstate. .hg/rebasestate and I suspect other
state files as well).

Not included in this series, I have ~30 commits, doubling test-shelve.t
in size and testing almost every tested shelve usecase for obs-shelve.
Here's the series for the curious now: http://pastebin.com/tGJKx0vM
I would like to send it to the mailing list and get accepted as well,
but:
1. it's big, so should I send like 6 patches a time or so?
2. instead of having a commit per test case, it more like
   a commit per some amount of copy-pasted code. I tried to keep
   it meaningful and named commits somewhat properly, but it is
   far from this list standards IMO. Any advice on how to get it
   in without turning it into a 100 commits and spending many
   days writing descriptions?
3. it makes test-shelve.t run for twice as long (and it is already
   a slow test). Newest test-shelve.r runs for ~1 minute.

diff --git a/hgext/shelve.py b/hgext/shelve.py
--- a/hgext/shelve.py
+++ b/hgext/shelve.py
@@ -62,7 +62,7 @@ testedwith = 'ships-with-hg-core'
 
 backupdir = 'shelve-backup'
 shelvedir = 'shelved'
-shelvefileextensions = ['hg', 'patch']
+shelvefileextensions = ['hg', 'patch', 'oshelve']
 # universal extension is present in all types of shelves
 patchextension = 'patch'
 
@@ -70,6 +70,9 @@ patchextension = 'patch'
 # generic user for all shelve operations
 shelveuser = 'shelve@localhost'
 
+class obsshelvefile(scmutil.simplekeyvaluefile):
+KEYS = [('node', True)]
+
 class shelvedfile(object):
 """Helper for the file storing a single shelve
 
@@ -153,6 +156,12 @@ class shelvedfile(object):
 bundle2.writebundle(self.ui, cg, self.fname, btype, self.vfs,
 compression=compression)
 
+def writeobsshelveinfo(self, info):
+obsshelvefile(self.vfs, self.fname).write(info)
+
+def readobsshelveinfo(self):
+return obsshelvefile(self.vfs, self.fname).read()
+
 class shelvedstate(object):
 """Handle persistence during unshelving operations.
 
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 2 of 2 V2] templater: add '{envvars}' to access environment variables

2017-01-19 Thread Yuya Nishihara
On Wed, 18 Jan 2017 21:39:47 -0500, Matt Harbison wrote:
> On Wed, 18 Jan 2017 08:44:55 -0500, Yuya Nishihara  wrote:
> 
> > On Tue, 17 Jan 2017 23:50:47 -0500, Matt Harbison wrote:
> >> # HG changeset patch
> >> # User Matt Harbison 
> >> # Date 1484712774 18000
> >> #  Tue Jan 17 23:12:54 2017 -0500
> >> # Node ID fce42f9dba7bab71463c0bcec44e6d87fdf275b2
> >> # Parent  5a03e25ec0c0417e915b2014995bd83443ef97ec
> >> templater: add '{envvars}' to access environment variables
> >>
> >> Since the option for ui.exportableenviron is experimental, so is this  
> >> template
> >> until the underlying API is sorted out.
> >>
> >> diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py
> >> --- a/mercurial/cmdutil.py
> >> +++ b/mercurial/cmdutil.py
> >> @@ -1448,7 +1448,8 @@
> >>  'parent': '{rev}:{node|formatnode} ',
> >>  'manifest': '{rev}:{node|formatnode}',
> >>  'file_copy': '{name} ({source})',
> >> -'extra': '{key}={value|stringescape}'
> >> +'extra': '{key}={value|stringescape}',
> >> +'envvar': '{key}={value|stringescape}'
> >
> > Dropped '|stringescape' since {envvar} should be readable text in  
> > general.
> 
> The only reason I thought it was necessary is because MSYS is apparently  
> putting a couple '\n' in $PS1.
> 
> $ ../hg log -r . -T "{get(envvars, 'PS1')}" --config  
> "experimental.exportableenviron=*"
> \[\033]0;$MSYSTEM:\w\007
> \033[32m\]\u@\h \[\033[33m\w\033[0m\]
> 
> vs
> 
> $ echo $PS1
> \[\033]0;$MSYSTEM:\w\007 \033[32m\]\u@\h \[\033[33m\w\033[0m\] $
> 
> Maybe that's too much of a corner case to care.  Dropping the escaping  
> also prevents the Windows paths from showing as 'C:\\Windows\\..', so  
> maybe that's a good thing.

Ah, thanks for the clarification. I think the Windows paths is more important,
so let's go without stringescape.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH 1 of 2 V2] ui: introduce an experimental dict of exportable environment variables

2017-01-19 Thread Yuya Nishihara
On Wed, 18 Jan 2017 21:32:34 -0500, Matt Harbison wrote:
> On Wed, 18 Jan 2017 08:52:43 -0500, Yuya Nishihara  wrote:
> > On Tue, 17 Jan 2017 23:50:46 -0500, Matt Harbison wrote:
> >> # HG changeset patch
> >> # User Matt Harbison 
> >> # Date 1484712312 18000
> >> #  Tue Jan 17 23:05:12 2017 -0500
> >> # Node ID 5a03e25ec0c0417e915b2014995bd83443ef97ec
> >> # Parent  923336cf8b8afdb41746ecef8a39d773bd5538bf
> >> ui: introduce an experimental dict of exportable environment variables
> >
> > This looks good as an experimental implementation, so queued, thanks.
> > I found a few minor problems, which can be fixed later.
> >
> >> Care needs to be taken to prevent leaking potentially sensitive  
> >> environment
> >> variables through hgweb, if template support for environment variables  
> >> is to be
> >> introduced.  There are a few ideas about the API for preventing  
> >> accidental
> >> leaking [1].  Option 3 seems best from the POV of not needing to  
> >> configure
> >> anything in the normal case.  I couldn't figure out how to do that, so  
> >> guard it
> >> with an experimental option for now.
> >>
> >> [1]  
> >> https://www.mercurial-scm.org/pipermail/mercurial-devel/2017-January/092383.html
> >
> > In addition to hgweb, we'll probably need to consider the case where hg
> > command is executed behind a third-party web application. A web frontend  
> > may
> > pass a revset provided by user for example, which seems a valid use case.
> 
> OK.  But this is well beyond anything I've done or understand, so I'll  
> need pointers if there's anything specific to this case.

Sorry for vague comment. I was thinking about which command options should
be considered safe for malicious input. For instance,

 a) a web application may accept ?rev=%REV% and execute "hg log -r'%REV%'",
which seems fine as long as '%REV%' is shell-escaped appropriately.
 b) but if we have 'search_in_template(pattern, template)' revset and
'{envvars}' template, (a) no longer be true.

So I saw (b) had the same story for 'shell(command)' revset/fileset proposed
before. We shouldn't allow shell injection in revset/fileset. Instead, we could
define an external revset/fileset command in config file.

Then, this question came up:

 c) how about -T? is a template fragment considered unsafe?

> > And we'll need to build the dict by fixconfig(), not by __init__().
> 
> Is there a way to know if ui is being used by hgweb or a web app?  I'd  
> prefer this --config setting go away, at least in the default case.  Maybe  
> it's useful if someone wants to opt in some vars for hgweb, for some  
> reason.

If hgweb is only the case, maybe we can overwrite ui._exportableenviron by
hgweb.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH STABLE] pager: wrap _runcommand() no matter if stdout is redirected

2017-01-19 Thread Yuya Nishihara
# HG changeset patch
# User Yuya Nishihara 
# Date 1484834492 -32400
#  Thu Jan 19 23:01:32 2017 +0900
# Branch stable
# Node ID f3ca8b7e0e2df7507661adf5957c51e39bc6b5b1
# Parent  262c2be8ea5a4f36fb7348cb5a940cf78f2dffa9
pager: wrap _runcommand() no matter if stdout is redirected

We've made chg utilize the common code path implemented in pager.py (by
815e1cefd082 and 493935e0327a), but the chg server does not always start
with a tty. Because of this, uisetup() of the pager extension could be
skipped on the chg server.

Kudos given to Sean Farley for dogfooding new chg and spotting this problem.

diff --git a/hgext/pager.py b/hgext/pager.py
--- a/hgext/pager.py
+++ b/hgext/pager.py
@@ -115,9 +115,6 @@ def _runpager(ui, p):
 pager.wait()
 
 def uisetup(ui):
-if '--debugger' in sys.argv or not ui.formatted():
-return
-
 class pagerui(ui.__class__):
 def _runpager(self, pagercmd):
 _runpager(self, pagercmd)
@@ -130,7 +127,7 @@ def uisetup(ui):
 always = util.parsebool(options['pager'])
 auto = options['pager'] == 'auto'
 
-if not p:
+if not p or '--debugger' in sys.argv or not ui.formatted():
 pass
 elif always:
 usepager = True
diff --git a/tests/test-chg.t b/tests/test-chg.t
--- a/tests/test-chg.t
+++ b/tests/test-chg.t
@@ -32,6 +32,38 @@ long socket path
 
   $ cd ..
 
+pager
+-
+
+  $ cat >> fakepager.py < import sys
+  > for line in sys.stdin:
+  > sys.stdout.write('paged! %r\n' % line)
+  > EOF
+
+enable pager extension globally, but spawns the master server with no tty:
+
+  $ chg init pager
+  $ cd pager
+  $ cat >> $HGRCPATH < [extensions]
+  > pager =
+  > [pager]
+  > pager = python $TESTTMP/fakepager.py
+  > EOF
+  $ chg version > /dev/null
+  $ touch foo
+  $ chg ci -qAm foo
+
+pager should be enabled if the attached client has a tty:
+
+  $ chg log -l1 -q --config ui.formatted=True
+  paged! '0:1f7b0de80e11\n'
+  $ chg log -l1 -q --config ui.formatted=False
+  0:1f7b0de80e11
+
+  $ cd ..
+
 server lifecycle
 
 
diff --git a/tests/test-pager.t b/tests/test-pager.t
--- a/tests/test-pager.t
+++ b/tests/test-pager.t
@@ -152,6 +152,11 @@ doesn't result in history being paged.
   summary: modify a 9
   
 
+Pager should not start if stdout is not a tty.
+
+  $ hg log -l1 -q --config ui.formatted=False
+  10:46106edeeb38
+
 Pager with color enabled allows colors to come through by default,
 even though stdout is no longer a tty.
   $ cat >> $HGRCPATH 

Re: [PATCH] ui: rename tmpdir parameter to more specific repopath

2017-01-19 Thread Yuya Nishihara
On Wed, 18 Jan 2017 18:38:54 -0800, Sean Farley wrote:
> # HG changeset patch
> # User Sean Farley 
> # Date 1484792751 28800
> #  Wed Jan 18 18:25:51 2017 -0800
> # Branch stable
> # Node ID 41d220e2bed95664c335f6a7ef70b8ce06dca86c
> # Parent  94af7d0c812fe7d3a5651191685ca43e1a331814
> ui: rename tmpdir parameter to more specific repopath
> 
> This was requested by Augie and I agree that repopath is more
> descriptive.
> 
> diff --git a/hgext/histedit.py b/hgext/histedit.py
> index ae860d8..3e11fff 100644
> --- a/hgext/histedit.py
> +++ b/hgext/histedit.py
> @@ -1333,11 +1333,11 @@ def ruleeditor(repo, ui, actions, editco
>  
>  rules = '\n'.join([act.torule() for act in actions])
>  rules += '\n\n'
>  rules += editcomment
>  rules = ui.edit(rules, ui.username(), {'prefix': 'histedit'},
> -tmpdir=repo.path)
> +repopath=repo.path)

repo.path could be set to ui at localrepository.__init__(), if we _always_
want to switch the tempdir. Just an idea, which is obviously out of stable
scope.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


Re: [PATCH STABLE] statprof: require input file

2017-01-19 Thread Yuya Nishihara
On Wed, 18 Jan 2017 23:11:44 -0800, Gregory Szorc wrote:
> # HG changeset patch
> # User Gregory Szorc 
> # Date 1484808307 28800
> #  Wed Jan 18 22:45:07 2017 -0800
> # Branch stable
> # Node ID c148ef85245edba14eff9219e0404fc832c8305c
> # Parent  94af7d0c812fe7d3a5651191685ca43e1a331814
> statprof: require input file

Queued this. Thanks for taking care of it.
___
Mercurial-devel mailing list
Mercurial-devel@mercurial-scm.org
https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel


[PATCH v9 RFC] scmutil: add a simple key-value file helper

2017-01-19 Thread Kostia Balytskyi
# HG changeset patch
# User Kostia Balytskyi 
# Date 1484824655 28800
#  Thu Jan 19 03:17:35 2017 -0800
# Node ID 19a449c91ef14e691cf1347748473e0094fedc86
# Parent  9f264adbe75bfae8551dc0e6e0fce8d43fc7b43a
scmutil: add a simple key-value file helper

The purpose of the added class is to serve purposes like save files of shelve
or state files of shelve, rebase and histedit. Keys of these files can be
alphanumeric and start with letters, while values must not contain newlines.
Keys which start with an uppercase letter are required, while other keys
are optional.

In light of Mercurial's reluctancy to use Python's json module, this tries
to provide a reasonable alternative for a non-nested named data.
Comparing to current approach of storing state in plain text files, where
semantic meaning of lines of text is only determined by their oreder,
simple key-value file allows for reordering lines and thus helps handle
optional values.

Initial use-case I see for this is obs-shelve's shelve files. Later we
can possibly migrate state files to this approach.

The test is in a new file beause I did not figure out where to put it
within existing test suite. If you give me a better idea, I will gladly
follow it.

diff --git a/mercurial/error.py b/mercurial/error.py
--- a/mercurial/error.py
+++ b/mercurial/error.py
@@ -246,3 +246,6 @@ class UnsupportedBundleSpecification(Exc
 
 class CorruptedState(Exception):
 """error raised when a command is not able to read its state from file"""
+
+class MissingRequiredKey(Exception):
+"""error raised when simple key-value file misses a required key"""
diff --git a/mercurial/scmutil.py b/mercurial/scmutil.py
--- a/mercurial/scmutil.py
+++ b/mercurial/scmutil.py
@@ -1571,3 +1571,51 @@ class checkambigatclosing(closewrapbase)
 def close(self):
 self._origfh.close()
 self._checkambig()
+
+class simplekeyvaluefile(object):
+"""A simple file with key=value lines
+
+Keys must be alphanumerics and start with a letter, values must not
+contain '\n' characters"""
+
+# if KEYS is non-empty, read values are validated against it:
+# each key is a tuple (keyname, required)
+KEYS = []
+
+def __init__(self, vfs, path):
+self.vfs = vfs
+self.path = path
+
+def validate(self, d):
+for key, req in self.KEYS:
+if req and key not in d:
+e = "missing a required key: '%s'" % key
+raise error.MissingRequiredKey(e)
+
+def read(self):
+lines = self.vfs.readlines(self.path)
+try:
+d = dict(line[:-1].split('=', 1) for line in lines if line)
+except ValueError as e:
+raise error.CorruptedState(str(e))
+self.validate(d)
+return d
+
+def write(self, data):
+"""Write key=>value mapping to a file
+data is a dict. Keys must be alphanumerical and start with a letter.
+Values must not contain newline characters."""
+lines = []
+for k, v in data.items():
+if not k[0].isalpha():
+e = "keys must start with a letter in a key-value file"
+raise error.ProgrammingError(e)
+if not k.isalnum():
+e = "invalid key name in a simple key-value file"
+raise error.ProgrammingError(e)
+if '\n' in v:
+e = "invalid value in a simple key-value file"
+raise error.ProgrammingError(e)
+lines.append("%s=%s\n" % (k, v))
+with self.vfs(self.path, mode='wb', atomictemp=True) as fp:
+fp.write(''.join(lines))
diff --git a/tests/test-simplekeyvaluefile.py b/tests/test-simplekeyvaluefile.py
new file mode 100644
--- /dev/null
+++ b/tests/test-simplekeyvaluefile.py
@@ -0,0 +1,87 @@
+from __future__ import absolute_import
+
+import unittest
+import silenttestrunner
+
+from mercurial import (
+error,
+scmutil,
+)
+
+contents = {}
+
+class fileobj(object):
+def __init__(self, name):
+self.name = name
+
+def __enter__(self):
+return self
+
+def __exit__(self, *args, **kwargs):
+pass
+
+def write(self, text):
+contents[self.name] = text
+
+def read(self):
+return contents[self.name]
+
+class mockvfs(object):
+def read(self, path):
+return fileobj(path).read()
+
+def readlines(self, path):
+return fileobj(path).read().split('\n')
+
+def __call__(self, path, mode, atomictemp):
+return fileobj(path)
+
+class testsimplekeyvaluefile(unittest.TestCase):
+def setUp(self):
+self.vfs = mockvfs()
+
+def testbasicwriting(self):
+d = {'key1': 'value1', 'Key2': 'value2'}
+scmutil.simplekeyvaluefile(self.vfs, 'kvfile').write(d)
+self.assertEqual(sorted(self.vfs.read('kvfile').split('\n')),
+ ['', 'Key2=value2', 'key1=value1'])
+
+def testinvalidkeys(self):
+d = {'0key1':