Page MenuHomePhabricator

Unable to push to hosted Mercurial repository over SSH with new upstream bookmark position
Closed, DuplicatePublic

Description

Steps to Reproduce

  1. With hosted Mercurial repository, ensure local clone is in matching state (local master bookmark points to same changeset upstream)
  2. Ensure master bookmark is active (hg up master)
  3. Modify some file, commit
  4. Attempt to push the changeset upstream using the SSH protocol
ssh-push-exception
cspeckrun@specktop ~/P/n/hgtest> hg pus --debug --traceback -v
no terminfo entry for sitm
no terminfo entry for dim
pushing to ssh://phab-devel.code/diffusion/HGTEST/hgtest/
running ssh phab-devel.code 'hg -R diffusion/HGTEST/hgtest/ serve --stdio'
sending hello command
sending between command
remote: 271
remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
remote: 1
query 1; heads
sending batch command
searching for changes
all remote heads known locally
preparing listkeys for "phases"
sending listkeys command
received listkey for "phases": 15 bytes
checking for updated bookmarks
preparing listkeys for "bookmarks"
sending listkeys command
received listkey for "bookmarks": 47 bytes
sending branchmap command
sending branchmap command
preparing listkeys for "bookmarks"
sending listkeys command
received listkey for "bookmarks": 47 bytes
1 changesets found
list of changesets:
1cb56b0576fbf0cb40b908453f8ff5d8032ac743
sending unbundle command
bundle2-output-bundle: "HG20", 5 parts total
bundle2-output-part: "replycaps" 155 bytes payload
bundle2-output-part: "check:heads" streamed payload
bundle2-output-part: "changegroup" (params: 1 mandatory) streamed payload
bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
bundle2-output-part: "pushkey" (params: 4 mandatory) empty payload
bundle2-input-bundle: with-transaction
bundle2-input-part: "reply:changegroup" (advisory) (params: 0 advisory) supported
bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
bundle2-input-part: total payload size 100
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
bundle2-input-part: "reply:pushkey" (params: 0 advisory) supported
bundle2-input-part: "output" (advisory) (params: 0 advisory) supported
bundle2-input-part: total payload size 1433
remote: [2016-01-18 22:08:23] EXCEPTION: (CommandException) Command failed with error #255!
remote: COMMAND
remote: hg log --template '{node}' --rev 'ancestor('\''cfd5b1ce30b509b88e520f54579bce7c48a237c1'\'', '\''1cb56b0576fbf0cb40b908453f8ff5d8032ac743'\'')'
remote: 
remote: STDOUT
remote: (empty)
remote: 
remote: STDERR
remote: abort: unknown revision '1cb56b0576fbf0cb40b908453f8ff5d8032ac743'!
remote:  at [<phutil>/src/future/exec/ExecFuture.php:416]
remote: arcanist(head=master, ref.master=b87138356a9c), phabricator(head=master, ref.master=7ab970d1a7d3), phutil(head=master, ref.master=c3fd3a8bb2c3)
remote:   #0 ExecFuture::resolvex() called at [<phabricator>/src/applications/repository/storage/PhabricatorRepository.php:443]
remote:   #1 PhabricatorRepository::execxLocalCommand(string, string, string) called at [<phabricator>/src/applications/diffusion/engine/DiffusionCommitHookEngine.php:973]
remote:   #2 DiffusionCommitHookEngine::findMercurialPushKeyRefUpdates() called at [<phabricator>/src/applications/diffusion/engine/DiffusionCommitHookEngine.php:698]
remote:   #3 DiffusionCommitHookEngine::findMercurialRefUpdates() called at [<phabricator>/src/applications/diffusion/engine/DiffusionCommitHookEngine.php:222]
remote:   #4 DiffusionCommitHookEngine::findRefUpdates() called at [<phabricator>/src/applications/diffusion/engine/DiffusionCommitHookEngine.php:124]
remote:   #5 DiffusionCommitHookEngine::execute() called at [<phabricator>/scripts/repository/commit_hook.php:133]
remote: pushkey-abort: prepushkey.phabricator hook exited with status 255
bundle2-input-part: "output" (advisory) supported
bundle2-input-bundle: 5 parts total
updating bookmark master failed!
preparing listkeys for "phases"
sending listkeys command
received listkey for "phases": 15 bytes

According to the output, the two arguments passed into the ancestor revset are

  1. e924773ad453bceb5122c5ac67f1fd5859f3e609 is the current master upstream, prior to any updates
  2. fb1da8a86b9ca51cb9f201b52edd0470ee808eda is the new changeset being pushed.

It looks like the prepushkey Mercurial hook might be running prior to the commit/changeset being applied, but from what I can tell this execution path is the first thing it does when the hook is executed, and hasn't changed recently.

As a precaution to the recent bundle2 changes, I tried commenting out these lines (and restarting services) - however that did not seem to affect anything:
https://secure.phabricator.com/diffusion/P/browse/master/src/applications/diffusion/ssh/DiffusionMercurialServeSSHWorkflow.php;cea633f698daad27d5e2b781124198d9f0767878$112-113

phabricatorcea633f698daad27d5e2b781124198d9f0767878
arcanist3308da5f8fbe9de8c9c2a25646a5704eca323851
libphutild5b4421792e5829b7a760c55dbf9adbc70669455
server hgMercurial Distributed SCM (version 3.5.1)
client hgMercurial Distributed SCM (version 3.5.2)

Workaround

  1. Align the master bookmark with server: hg book -r xxx master -f
  2. Push outgoing commits: hg pus
  3. Update master to now-public changeset: hg book -r tip master
  4. Push just the bookmark: hg pus -r master

What's weird is that pushing over http seems to work fine:

http-push
cspeckrun@specktop ~/P/n/hgtest> hg pus http
pushing to http://Administrator:***@192.168.0.133/diffusion/HGTEST/hgtest
searching for changes
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: added 1 changesets with 1 changes to 1 files
updating bookmark master

Testing on rHGTEST

This is reproducible on rHGTEST using hg 3.6.3 - the error output is a little different from above, which was done using an earlier version of hg on the client.

rHGTEST push exception
cspeckrun@specktop ~/P/p/hg-test> hg pus --debug --traceback -v
no terminfo entry for sitm
no terminfo entry for dim
pushing to ssh://dweller@secure.phabricator.com/diffusion/HGTEST/hg-test/
running ssh dweller@secure.phabricator.com 'hg -R diffusion/HGTEST/hg-test/ serve --stdio'
sending hello command
sending between command
remote: 145
remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
remote: 1
query 1; heads
sending batch command
searching for changes
all remote heads known locally
preparing listkeys for "phases"
sending listkeys command
received listkey for "phases": 15 bytes
checking for updated bookmarks
preparing listkeys for "bookmarks"
sending listkeys command
received listkey for "bookmarks": 47 bytes
sending branchmap command
sending branchmap command
preparing listkeys for "bookmarks"
sending listkeys command
received listkey for "bookmarks": 47 bytes
1 changesets found
list of changesets:
2a4fde99cf721d394a19f75208ff6c3b6d94e4bd
sending unbundle command
remote: abort: stream ended unexpectedly (got 0 bytes, expected 6)
Traceback (most recent call last):
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/dispatch.py", line 187, in _runcatch
    return _dispatch(req)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/dispatch.py", line 920, in _dispatch
    cmdpats, cmdoptions)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/dispatch.py", line 679, in runcommand
    ret = _runcommand(ui, options, cmd, d)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/extensions.py", line 183, in closure
    return func(*(args + a), **kw)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/hgext/pager.py", line 139, in pagecmd
    return orig(ui, options, cmd, cmdfunc)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/extensions.py", line 183, in closure
    return func(*(args + a), **kw)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/hgext/color.py", line 525, in colorcmd
    return orig(ui_, opts, cmd, cmdfunc)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/dispatch.py", line 1051, in _runcommand
    return checkargs()
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/dispatch.py", line 1011, in checkargs
    return cmdfunc()
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/dispatch.py", line 917, in <lambda>
    d = lambda: util.checksignature(func)(ui, *args, **cmdoptions)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/util.py", line 801, in check
    return func(*args, **kwargs)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/extensions.py", line 183, in closure
    return func(*(args + a), **kw)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/util.py", line 801, in check
    return func(*args, **kwargs)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/hgext/mq.py", line 3528, in mqcommand
    return orig(ui, repo, *args, **kwargs)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/util.py", line 801, in check
    return func(*args, **kwargs)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/commands.py", line 5428, in push
    opargs=opts.get('opargs'))
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/exchange.py", line 373, in push
    _pushchangeset(pushop)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/exchange.py", line 854, in _pushchangeset
    pushop.repo.url())
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/wireproto.py", line 351, in unbundle
    ret, output = self._callpush("unbundle", cg, heads=heads)
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/sshpeer.py", line 271, in _callpush
    r = self._recv()
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/sshpeer.py", line 299, in _recv
    self._abort(error.ResponseError(_("unexpected response:"), l))
  File "/usr/local/Cellar/mercurial/3.6.3/lib/python2.7/site-packages/mercurial/sshpeer.py", line 212, in _abort
    raise exception
ResponseError: ('unexpected response:', '')
abort: unexpected response: empty string

Event Timeline

cspeckmim updated the task description. (Show Details)
cspeckmim added projects: Mercurial, Diffusion.
cspeckmim added a subscriber: cspeckmim.

Ugh so I can reproduce this regularly on the one repository I have (with fresh commits following steps to reproduce), but I tried creating a new repository to test in and I can't reproduce it there. I'm not sure what happened to the repository I see this in.

One last test - changing the server Mercurial version to 2.6.2 resolved this issue. I also tried commenting out this line (and restarting services):
https://secure.phabricator.com/diffusion/P/browse/master/src/applications/diffusion/query/lowlevel/DiffusionLowLevelMercurialPathsQuery.php;cea633f698daad27d5e2b781124198d9f0767878$31

Just as an additional precaution - and it did not resolve the issue - though both of the recent changes to Mercurial I had tested SSH/HTTP push prior to submitting the changes - which makes me think something happened to my repository in the past week or so.

This comment was removed by jgelens.
cspeckmim updated the task description. (Show Details)

I think I may see what the issue is. I believe the 'hello' command returns its list of capabilities, which is not filtered by T9450. I'm trying to test out some fixes but having difficulty with nano~

The mercurial server is sending down capabilities which includes bundle2 at the beginning of the remote SSH during push (which tells the client it can use new bundle2). It's not sent as part of a command which the client invokes which is what T9450 is handling. Instead when the mercurial command server initializes it immediately sends over a list of capabilities.

I did some poking at PhabricatorSSHPassthruCommand and I don't 100% follow how this whole thing is setup, but I believe the answer will involve using a WillReadCallback in DiffusionMercurialServeSSHWorkflow to intercept capabilities message going down.

I tried adding a read callback, and I'm definitely intercepting capabilities: ... at that point (which includes bundle2), however I haven't figured out how to properly parse the data. In the read callback I'm processing the data like this:

throw new Exception('THIS IS LINE:'.$message.':END OF LINE');

And this is what I get:

cspeckrun@specktop ~/P/n/hgtest> hg pus --debug --traceback -v
no terminfo entry for sitm
no terminfo entry for dim
pushing to ssh://phab-devel.code/diffusion/HGTEST/hgtest/
running ssh phab-devel.code 'hg -R diffusion/HGTEST/hgtest/ serve --stdio'
sending hello command
sending between command
remote: Exception: THIS IS LINE:271
remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream bundle2=HG20%0Achangegroup%3D01%2C02%0Adigests%3Dmd5%2Csha1%2Csha512%0Alistkeys%0Apushkey%0Aremote-changegroup%3Dhttp%2Chttps unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
remote: 1
remote: 
remote: :END OF LINE

I tried splitting on \n and remote: then checking each line if it starts with capabilities: (neither worked) - I'm having a super difficult time trying to dump that data as-is to some location where I can examine it. May need a helping hand from @epriestley.

It's late...

I just did a fwrite of the contents to look at. It turns out my parsing logic was initially correct however PHP is a saboteur -- '\n' is literal two-characters (unless in double-quotes, apparently?), so exploding/imploding was not doing what I wanted.

With that resolved my changes seem to fix this - I should be able to get this cleaned up in a revision/UT in a day or two.

I am on Mercurial 3.6 on both, the server and the client, and just tried what @cspeckmim suggested: Removing bundle2 from the capabilities in reply to the hello command. Now my client gets one step further, but still fails to push most of the time with remote: abort: stream ended unexpectedly (got 0 bytes, expected 4)

@andyboeh - I think there might be an issue with some of the fixes I was looking at. When removing the bundle2 capability from reply, the message preceding the capabilities output is a length of the following line, usually something like

242
capabilities: a, b, c, d, bundle2=http%%%%%...

By just removing bundle2 it causes the preceding number to be off, which is what the client uses for parsing the stream. I haven't gotten back to looking at this but I think that's the issue you're seeing.

@cspeckmim I just fixed that, unfortunately, it doesn't help. Here is a complete debug output when trying to push:

sending hello command
sending between command
remote: 145
remote: capabilities: lookup changegroupsubset branchmap pushkey known getbundle unbundlehash batch stream unbundle=HG10GZ,HG10BZ,HG10UN httpheader=1024
remote: 1
query 1; heads
sending batch command
searching for changes
all remote heads known locally
preparing listkeys for "phases"
sending listkeys command
received listkey for "phases": 15 bytes
checking for updated bookmarks
preparing listkeys for "bookmarks"
sending listkeys command
received listkey for "bookmarks": 0 bytes
sending branchmap command
sending branchmap command
preparing listkeys for "bookmarks"
sending listkeys command
received listkey for "bookmarks": 0 bytes
1 changesets found
list of changesets:
96b4a77e0e0b3158dbf049a8a046a9b78114203a
sending unbundle command
remote: adding changesets
remote: adding manifests
remote: adding file changes
remote: transaction abort!
remote: rollback completed
remote: abort: stream ended unexpectedly (got 1953 bytes, expected 4225)
preparing listkeys for "phases"
sending listkeys command
received listkey for "phases": 0 bytes

As you can see, this time it received more than 0 bytes, the number of bytes read changes sometimes (I can rule out the network because it works perfectly fine with 3.4 as client).

The willReadMessageCallback is as simple as this:

public function willReadMessageCallback(
  PhabricatorSSHPassthruCommand $command,
  $message) {

  $parts = explode("\n", $message);

  if((count($parts) > 1) && isset($parts[1]) && (strpos($parts[1], "capabilities:") === 0))
  {
    $parts[1] = DiffusionMercurialWireProtocol::filterBundle2Capability($parts[1]);
    $parts[0] = strlen($parts[1]) + 1; // The '\n' seems to be counted, too
    $message = implode("\n", $parts);
  }

  return $message;
}

I'm just going to merge this into T9548 since I think bundle2 support will fix it?