[PATCH 2 of 2] test-split: stabilize for Windows
# HG changeset patch # User Matt Harbison # Date 1513928288 18000 # Fri Dec 22 02:38:08 2017 -0500 # Node ID feaadb8201a95e7a3f36ab3d62c1f942c4faa297 # Parent e3de939009d730b1e26ba37b59a0d2178adef6ef test-split: stabilize for Windows - $PYTHON needs to be quoted when used as an executable in $HGEDITOR. This avoids the error "'c' is not recognized as an internal or external command". - seq.py is printing out CRLF, and then the subsequent `sed` script seems to convert to LF on MSYS. IDK if python print statements can be made to print LF on Windows, and I'm pretty sure CRLF is baked into some other tests. - A stray glob was causing the 'obsstore-off' case to report 'no result code from test'. - When I ran with --debug, the `hg diff` commands in the test both printed color sequences, and paused the output as it was run through the pager. diff --git a/tests/test-split.t b/tests/test-split.t --- a/tests/test-split.t +++ b/tests/test-split.t @@ -20,6 +20,8 @@ > split= > [ui] > interactive=1 + > color=no + > paginate=never > [diff] > git=1 > unified=0 @@ -57,9 +59,10 @@ abort: cannot split working directory [255] -Generate some content +Generate some content. The sed filter drop CR on Windows, which is dropped in +the a > b line. - $ $TESTDIR/seq.py 1 5 >> a + $ $TESTDIR/seq.py 1 5 | sed 's/\r$//' >> a $ hg ci -m a1 -A a -q $ hg bookmark -i r1 $ sed 's/1/11/;s/3/33/;s/5/55/' a > b @@ -132,7 +135,7 @@ [255] $ hg status - $ HGEDITOR="$PYTHON $TESTTMP/editor.py" + $ HGEDITOR="\"$PYTHON\" $TESTTMP/editor.py" $ runsplit diff --git a/a b/a 1 hunks, 1 lines changed @@ -197,7 +200,7 @@ EDITOR: HG: user: test EDITOR: HG: branch 'default' EDITOR: HG: changed a - saved backup bundle to $TESTTMP/a/.hg/strip-backup/1df0d5c5a3ab-8341b760-split.hg (glob) (obsstore-off !) + saved backup bundle to $TESTTMP/a/.hg/strip-backup/1df0d5c5a3ab-8341b760-split.hg (obsstore-off !) #if obsstore-off $ hg bookmark ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 1 of 2] lfs: use ui.note() and ui.debug() instead of ui.write() and their flags
# HG changeset patch # User Matt Harbison # Date 1513917106 18000 # Thu Dec 21 23:31:46 2017 -0500 # Node ID e3de939009d730b1e26ba37b59a0d2178adef6ef # Parent bb6a80fc969a2c5da80cbb7f29de7ca576c4e251 lfs: use ui.note() and ui.debug() instead of ui.write() and their flags Even though the upload/download message is still in a ui.verbose check, I switched that to ui.note() too so that the 'ui.note' label is applied. The debug message is no longer marked for translation because check-code complained. diff --git a/hgext/lfs/blobstore.py b/hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py +++ b/hgext/lfs/blobstore.py @@ -290,9 +290,9 @@ sizes[obj.get('oid')] = obj.get('size', 0) topic = {'upload': _('lfs uploading'), 'download': _('lfs downloading')}[action] -if self.ui.verbose and len(objects) > 1: -self.ui.write(_('lfs: need to transfer %d objects (%s)\n') - % (len(objects), util.bytecount(total))) +if len(objects) > 1: +self.ui.note(_('lfs: need to transfer %d objects (%s)\n') + % (len(objects), util.bytecount(total))) self.ui.progress(topic, 0, total=total) def transfer(chunk): for obj in chunk: @@ -302,8 +302,8 @@ msg = _('lfs: downloading %s (%s)\n') elif action == 'upload': msg = _('lfs: uploading %s (%s)\n') -self.ui.write(msg % (obj.get('oid'), - util.bytecount(objsize))) +self.ui.note(msg % (obj.get('oid'), + util.bytecount(objsize))) retry = self.retry while True: try: @@ -312,10 +312,9 @@ break except socket.error as ex: if retry > 0: -if self.ui.verbose: -self.ui.write( -_('lfs: failed: %r (remaining retry %d)\n') -% (ex, retry)) +self.ui.note( +_('lfs: failed: %r (remaining retry %d)\n') +% (ex, retry)) retry -= 1 continue raise @@ -326,8 +325,7 @@ for _one, oid in oids: processed += sizes[oid] self.ui.progress(topic, processed, total=total) -if self.ui.verbose: -self.ui.write(_('lfs: processed: %s\n') % oid) +self.ui.note(_('lfs: processed: %s\n') % oid) self.ui.progress(topic, pos=None, total=total) def __del__(self): diff --git a/hgext/lfs/wrapper.py b/hgext/lfs/wrapper.py --- a/hgext/lfs/wrapper.py +++ b/hgext/lfs/wrapper.py @@ -273,9 +273,7 @@ def extractpointers(repo, revs): """return a list of lfs pointers added by given revs""" -ui = repo.ui -if ui.debugflag: -ui.write(_('lfs: computing set of blobs to upload\n')) +repo.ui.debug('lfs: computing set of blobs to upload\n') pointers = {} for r in revs: ctx = repo[r] ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 7 of 7] lfs: only hardlink between the usercache and local store if the blob verifies
> On Dec 21, 2017, at 14:39, Matt Harbison wrote: > > # HG changeset patch > # User Matt Harbison > # Date 1513883619 18000 > # Thu Dec 21 14:13:39 2017 -0500 > # Node ID 909f1310d199e4b5f542e1e72b07f2bf992dd7a7 > # Parent d86777c0c198848df6e9083272685a202bebe534 > lfs: only hardlink between the usercache and local store if the blob verifies Queued the series, thanks. pacem in terris / мир / शान्ति / سَلاَم / 平和 Kevin R. Bullock ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 3 of 7] lfs: add note messages indicating what store holds the lfs blob
> On Dec 21, 2017, at 14:39, Matt Harbison wrote: > > # HG changeset patch > # User Matt Harbison > # Date 1513724024 18000 > # Tue Dec 19 17:53:44 2017 -0500 > # Node ID 1a68987ba0c3fa2494a8fae8321cddd0876efd7f > # Parent 00f733da93f18119d262e988985661c55c1707c3 > lfs: add note messages indicating what store holds the lfs blob > > The following corruption related patches were written prior to adding the user > level cache, and it took awhile to track down why the tests changed. (It > generally made things more resilient.) But I think this will be useful to the > end user as well. I didn't make it --debug level, because there can be a ton > of > info coming out of clone/push/pull --debug. The pointers are sorted for test > stability. > > I opted for ui.note() instead of checking ui.verbose and then using ui.write() > for convenience, but I see most of this extension does the latter. I have no > idea what the preferred form is. I think ui.note() is better for these circumstances. Checking ui.verbose is useful in more complicated situations, e.g. where (1) you're checking another condition along with verbosity, or (2) you're using formatter.condwrite(). pacem in terris / мир / शान्ति / سَلاَم / 平和 Kevin R. Bullock ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 4 of 4] templater: look up symbols/resources as if they were separated (issue5699)
> On Dec 21, 2017, at 09:08, Yuya Nishihara wrote: > > # HG changeset patch > # User Yuya Nishihara > # Date 1513862259 -32400 > # Thu Dec 21 22:17:39 2017 +0900 > # Node ID 9d50cfedc380d05dfbe0ed8b9360603b61b3c601 > # Parent c06827f22f361f8e1ff3431c18ed0734708149ed > templater: look up symbols/resources as if they were separated (issue5699) Queued the series, thanks. pacem in terris / мир / शान्ति / سَلاَم / 平和 Kevin R. Bullock ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
mercurial@35464: 7 new changesets
7 new changesets in mercurial: https://www.mercurial-scm.org/repo/hg/rev/5bec509dc1ff changeset: 35458:5bec509dc1ff user:Yuya Nishihara date:Tue Dec 19 21:41:39 2017 +0900 summary: log: make "slowpath" condition slightly more readable https://www.mercurial-scm.org/repo/hg/rev/b520c8f98e1e changeset: 35459:b520c8f98e1e user:Yuya Nishihara date:Mon Dec 18 21:15:53 2017 +0900 summary: sshpeer: move docstring to top https://www.mercurial-scm.org/repo/hg/rev/8652ab4046e4 changeset: 35460:8652ab4046e4 user:Jun Wu date:Wed Dec 20 02:13:35 2017 -0800 summary: osutil: add a function to unblock signals https://www.mercurial-scm.org/repo/hg/rev/3a119a423953 changeset: 35461:3a119a423953 user:Jun Wu date:Wed Dec 20 11:35:38 2017 -0800 summary: commandserver: unblock SIGCHLD https://www.mercurial-scm.org/repo/hg/rev/6f754b0fe54e changeset: 35462:6f754b0fe54e user:Jun Wu date:Wed Dec 20 16:44:35 2017 -0800 summary: journal: use pager https://www.mercurial-scm.org/repo/hg/rev/ef7e667a4f7b changeset: 35463:ef7e667a4f7b user:Phil Cohen date:Wed Dec 20 17:22:16 2017 -0600 summary: filemerge: only raise InMemoryMergeConflictsError when running _xmerge https://www.mercurial-scm.org/repo/hg/rev/6915f6a40283 changeset: 35464:6915f6a40283 bookmark:@ tag: tip user:Anton Shestakov date:Thu Dec 21 21:35:20 2017 +0800 summary: paper: minor adjustments to table styles -- 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
D1720: debug: add newlines at the end of three locations that appear to need it
This revision was automatically updated to reflect the committed changes. Closed by commit rHG7906354cbc68: debug: add newlines at the end of three locations that appear to need it (authored by spectral, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1720?vs=4536&id=4574 REVISION DETAIL https://phab.mercurial-scm.org/D1720 AFFECTED FILES hgext/patchbomb.py mercurial/bundle2.py mercurial/dispatch.py CHANGE DETAILS diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py --- a/mercurial/dispatch.py +++ b/mercurial/dispatch.py @@ -451,7 +451,7 @@ return m.group() else: ui.debug("No argument found for substitution " - "of %i variable in alias '%s' definition." + "of %i variable in alias '%s' definition.\n" % (int(m.groups()[0]), self.name)) return '' cmd = re.sub(br'\$(\d+|\$)', _checkvar, self.definition[1:]) diff --git a/mercurial/bundle2.py b/mercurial/bundle2.py --- a/mercurial/bundle2.py +++ b/mercurial/bundle2.py @@ -2056,7 +2056,7 @@ # The mergemarkers call will crash if marker creation is not enabled. # we want to avoid this if the part is advisory. if not inpart.mandatory and op.repo.obsstore.readonly: -op.repo.ui.debug('ignoring obsolescence markers, feature not enabled') +op.repo.ui.debug('ignoring obsolescence markers, feature not enabled\n') return new = op.repo.obsstore.mergemarkers(tr, markerdata) op.repo.invalidatevolatilesets() diff --git a/hgext/patchbomb.py b/hgext/patchbomb.py --- a/hgext/patchbomb.py +++ b/hgext/patchbomb.py @@ -632,15 +632,15 @@ # check if revision exist on the public destination publicurl = repo.ui.config('patchbomb', 'publicurl') if publicurl: -repo.ui.debug('checking that revision exist in the public repo') +repo.ui.debug('checking that revision exist in the public repo\n') try: publicpeer = hg.peer(repo, {}, publicurl) except error.RepoError: repo.ui.write_err(_('unable to access public repo: %s\n') % publicurl) raise if not publicpeer.capable('known'): -repo.ui.debug('skipping existence checks: public repo too old') +repo.ui.debug('skipping existence checks: public repo too old\n') else: out = [repo[r] for r in revs] known = publicpeer.known(h.node() for h in out) To: spectral, #hg-reviewers, krbullock Cc: krbullock, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1719: debug: remove an 'if ui.debug()' that is not doing anything
This revision was automatically updated to reflect the committed changes. Closed by commit rHG3bb1a647ab42: debug: remove an 'if ui.debug()' that is not doing anything (authored by spectral, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1719?vs=4535&id=4573 REVISION DETAIL https://phab.mercurial-scm.org/D1719 AFFECTED FILES mercurial/debugcommands.py CHANGE DETAILS diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -2307,10 +2307,6 @@ cache = {} ctx2str = str node2str = short -if ui.debug(): -def ctx2str(ctx): -return ctx.hex() -node2str = hex for rev in scmutil.revrange(repo, revs): ctx = repo[rev] ui.write('%s\n'% ctx2str(ctx)) To: spectral, #hg-reviewers, krbullock Cc: krbullock, quark, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1720: debug: add newlines at the end of three locations that appear to need it
krbullock accepted this revision. krbullock added a comment. This revision is now accepted and ready to land. Sure, queued. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1720 To: spectral, #hg-reviewers, krbullock Cc: krbullock, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1719: debug: remove an 'if ui.debug()' that is not doing anything
krbullock accepted this revision. krbullock added a comment. This revision is now accepted and ready to land. Seems reasonable. REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1719 To: spectral, #hg-reviewers, krbullock Cc: krbullock, quark, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1591: visibility: improve the message when accessing filtered obsolete rev
krbullock requested changes to this revision. krbullock added a comment. This revision now requires changes to proceed. One nit, otherwise looks nice! INLINE COMMENTS > obsutil.py:780 > +else: > +return 'superseed_split' > + These should be 'superseded' and 'superseded_split' (in the docstring above as well as here) REPOSITORY rHG Mercurial REVISION DETAIL https://phab.mercurial-scm.org/D1591 To: lothiraldan, #hg-reviewers, durin42, pulkit, krbullock Cc: krbullock, pulkit, durin42, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH 3 of 3] test-ssh: convert dumpenv to python for Windows
> On Dec 17, 2017, at 2:42 PM, Matt Harbison wrote: > > >> On Dec 17, 2017, at 2:07 PM, Gregory Szorc wrote: >> >>> On Sun, Dec 17, 2017 at 10:44 AM, Matt Harbison >>> wrote: >>> >>> > On Dec 16, 2017, at 7:27 PM, Matt Harbison wrote: >>> > >>> > # HG changeset patch >>> > # User Matt Harbison >>> > # Date 1513468682 18000 >>> > # Sat Dec 16 18:58:02 2017 -0500 >>> > # Node ID ca9cde0697c1e923f4c9a3396343afc238ba92f2 >>> > # Parent d1fbb07b0b7259ae4ca88434e0eda2a21359f7ec >>> > test-ssh: convert dumpenv to python for Windows >>> > >>> > Previously, this complained: >>> > >>> >remote: '.' is not recognized as an internal or external command, >>> >remote: operable program or batch file. >>> > >>> > 1 out of 4 Linux runs failed to print 17 after converting, and instead >>> > printed >>> > an empty remote line. Not sure why, but maybe the flush will help. >>> > >>> > diff --git a/tests/test-ssh.t b/tests/test-ssh.t >>> > --- a/tests/test-ssh.t >>> > +++ b/tests/test-ssh.t >>> > @@ -598,17 +598,19 @@ >>> > [255] >>> > >>> > test that custom environment is passed down to ssh executable >>> > - $ cat >>dumpenv <>> > - > #! /bin/sh >>> > - > echo \$VAR >&2 >>> > + $ cat >>dumpenv.py <>> > + > from __future__ import print_function >>> > + > import sys >>> > + > from mercurial import encoding >>> > + > print('%s' % encoding.environ.get('VAR', ''), file=sys.stderr) >>> > + > sys.stderr.flush() >>> >> EOF >>> > - $ chmod +x dumpenv >>> > - $ hg pull ssh://something --config ui.ssh="./dumpenv" >>> > + $ hg pull ssh://something --config ui.ssh="$PYTHON dumpenv.py" >>> > pulling from ssh://something/ >>> > remote: >>> > abort: no suitable response from remote hg! >>> > [255] >>> > - $ hg pull ssh://something --config ui.ssh="./dumpenv" --config >>> > sshenv.VAR=17 >>> > + $ hg pull ssh://something --config ui.ssh="$PYTHON dumpenv.py" >>> > --config sshenv.VAR=17 >>> > pulling from ssh://something/ >>> > remote: 17 >>> >>> No idea why, but if I run this in my Linux VM with -j9, it wants to append >>> an extra “remote: “ at this point. If run without workers, it appears >>> stable. >> >> The flush() shouldn't be necessary because stdio descriptors will get >> flushed when process exits. The new Python appears the same as the old shell >> script. > > That was my understanding of things too, but before I added the flush, the > instability seemed to be to omit the “remote: 17” line (but add the empty > remote line). > >> I suspect we are encountering a preexisting race condition. I suspect the >> Python version is more susceptible to it because of Python process startup >> overhead. Before, the shell script likely ran in ~1ms. Now, it takes >10ms. >> That may be enough to perturb the race condition. An additional data point: I ran without workers, with —loop, and it failed the same way in 3 out of 444 runs on the same Linux VM. >>> > abort: no suitable response from remote hg! >>> ___ >>> 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
[PATCH 6 of 7] lfs: verify lfs object content when transferring to and from the remote store
# HG changeset patch # User Matt Harbison # Date 1510895205 18000 # Fri Nov 17 00:06:45 2017 -0500 # Node ID d86777c0c198848df6e9083272685a202bebe534 # Parent 3ffb73f5ab3b12511008dc8496c913bd639f3f38 lfs: verify lfs object content when transferring to and from the remote store This avoids inserting corrupt files into the usercache, and local and remote stores. One down side is that the bad file won't be available locally for forensic purposes after a remote download. I'm thinking about adding an 'incoming' directory to the local lfs store to handle the download, and then move it to the 'objects' directory after it passes verification. That would have the additional benefit of not concatenating each transfer chunk in memory until the full file is transferred. Verification isn't needed when the data is passed back through the revlog interface or when the oid was just calculated, but otherwise it is on by default. The additional overhead should be well worth avoiding problems with file based remote stores, or buggy lfs servers. Having two different verify functions is a little sad, but the full data of the blob is mostly passed around in memory, because that's what the revlog interface wants. The upload function, however, chunks up the data. It would be ideal if that was how the content is always handled, but that's probably a huge project. I don't really like printing the long hash, but `hg debugdata` isn't a public interface, and is the only way to get it. The filelog and revision info is nowhere near this area, so recommending `hg verify` is the easiest thing to do. diff --git a/hgext/lfs/blobstore.py b/hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py +++ b/hgext/lfs/blobstore.py @@ -7,6 +7,7 @@ from __future__ import absolute_import +import hashlib import json import os import re @@ -99,8 +100,11 @@ self.cachevfs = lfsvfs(usercache) self.ui = repo.ui -def write(self, oid, data): +def write(self, oid, data, verify=True): """Write blob to local blobstore.""" +if verify: +_verify(oid, data) + with self.vfs(oid, 'wb', atomictemp=True) as fp: fp.write(data) @@ -110,14 +114,23 @@ self.ui.note(_('lfs: adding %s to the usercache\n') % oid) lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) -def read(self, oid): +def read(self, oid, verify=True): """Read blob from local blobstore.""" if not self.vfs.exists(oid): +blob = self._read(self.cachevfs, oid, verify) +self.ui.note(_('lfs: found %s in the usercache\n') % oid) lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) -self.ui.note(_('lfs: found %s in the usercache\n') % oid) else: self.ui.note(_('lfs: found %s in the local lfs store\n') % oid) -return self.vfs.read(oid) +blob = self._read(self.vfs, oid, verify) +return blob + +def _read(self, vfs, oid, verify): +"""Read blob (after verifying) from the given store""" +blob = vfs.read(oid) +if verify: +_verify(oid, blob) +return blob def has(self, oid): """Returns True if the local blobstore contains the requested blob, @@ -233,6 +246,8 @@ request = util.urlreq.request(href) if action == 'upload': # If uploading blobs, read data from local blobstore. +with localstore.vfs(oid) as fp: +_verifyfile(oid, fp) request.data = filewithprogress(localstore.vfs(oid), None) request.get_method = lambda: 'PUT' @@ -253,7 +268,7 @@ if action == 'download': # If downloading blobs, store downloaded data to local blobstore -localstore.write(oid, response) +localstore.write(oid, response, verify=True) def _batch(self, pointers, localstore, action): if action not in ['upload', 'download']: @@ -324,14 +339,14 @@ def writebatch(self, pointers, fromstore): for p in pointers: -content = fromstore.read(p.oid()) +content = fromstore.read(p.oid(), verify=True) with self.vfs(p.oid(), 'wb', atomictemp=True) as fp: fp.write(content) def readbatch(self, pointers, tostore): for p in pointers: content = self.vfs.read(p.oid()) -tostore.write(p.oid(), content) +tostore.write(p.oid(), content, verify=True) class _nullremote(object): """Null store storing blobs to /dev/null.""" @@ -368,6 +383,24 @@ None: _promptremote, } +def _verify(oid, content): +realoid = hashlib.sha256(content).hexdigest() +if realoid != oid: +raise error.Abort(_('detected corrupt lfs object: %s') % oid, + hint=_('run hg verify')) + +def _verifyfile(oid, fp): +sha256 = hashlib.sha256() +while True: +data = fp
[PATCH 4 of 7] test-lfs: add tests around corrupted lfs objects
# HG changeset patch # User Matt Harbison # Date 1510890773 18000 # Thu Nov 16 22:52:53 2017 -0500 # Node ID 51a98a20b44618eade6d9227cc8c5473b44450f5 # Parent 1a68987ba0c3fa2494a8fae8321cddd0876efd7f test-lfs: add tests around corrupted lfs objects These are mostly tests against file:// based remote stores, because that's what we have the most control over. The test uploading a corrupt blob to lfs-test-server demonstrates an overly broad exception handler in the retry loop. A corrupt blob is actually transferred in a download, but eventually caught when it is accessed (only after it leaves the corrupt file in a couple places locally). I don't think we want to trust random 3rd party implementations, and this would be a problem if there were a `debuglfsdownload` command that simply cached the files. And given the cryptic errors, we should probably validate the file hash locally before uploading, and also after downloading. diff --git a/tests/test-lfs-test-server.t b/tests/test-lfs-test-server.t --- a/tests/test-lfs-test-server.t +++ b/tests/test-lfs-test-server.t @@ -105,6 +105,55 @@ lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store 3 files updated, 0 files merged, 0 files removed, 0 files unresolved +Test a corrupt file download, but clear the cache first to force a download + +XXX: ideally, the validation would occur before polluting the usercache and +local store, with a clearer error message. + + $ rm -rf `hg config lfs.usercache` + $ cp $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 blob + $ echo 'damage' > $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + $ rm ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + $ rm ../repo1/* + $ hg --repo ../repo1 update -C tip -v + resolving manifests + getting a + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + getting b + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store + getting c + lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) + lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache + lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store + abort: integrity check failed on data/c.i:0! + [255] + +BUG: the corrupted blob was added to the usercache and local store + + $ cat ../repo1/.hg/store/lfs/objects/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | $TESTDIR/f --sha256 + sha256=fa82ca222fc9813afad3559637960bf311170cdd80ed35287f4623eb2320a660 + $ cat `hg config lfs.usercache`/d1/1e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 | $TESTDIR/f --sha256 + sha256=fa82ca222fc9813afad3559637960bf311170cdd80ed35287f4623eb2320a660 + $ cp blob $TESTTMP/lfs-content/d1/1e/1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + +Test a corrupted file upload + + $ echo 'another lfs blob' > b + $ hg ci -m 'another blob' + $ echo 'damage' > .hg/store/lfs/objects/e6/59058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 + $ hg push -v ../repo1 + pushing to ../repo1 + searching for changes + lfs: uploading e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 (17 bytes) + lfs: failed: LfsRemoteError('HTTP error: HTTP Error 500: Internal Server Error (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 5) + lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 4) + lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 3) + lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 2) + lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 1) + abort: HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)! + [255] + Check error message when the remote missed a blob: $ echo F > b diff --git a/tests/test-lfs.t b/tests/test-lfs.t --- a/tests/test-lfs.t +++ b/tests/test-lfs.t @@ -605,6 +605,193 @@ added 3 changesets with 2 changes to 1 files $ hg -R repo14 -q verify +Test damaged file scenarios. (This also damages the usercache because of the +hardlinks.) + +
[PATCH 7 of 7] lfs: only hardlink between the usercache and local store if the blob verifies
# HG changeset patch # User Matt Harbison # Date 1513883619 18000 # Thu Dec 21 14:13:39 2017 -0500 # Node ID 909f1310d199e4b5f542e1e72b07f2bf992dd7a7 # Parent d86777c0c198848df6e9083272685a202bebe534 lfs: only hardlink between the usercache and local store if the blob verifies This fixes the issue where verify (and other read commands) would propagate corrupt blobs. I originalled coded this to only hardlink if 'verify=True' for store.read(), but then good blobs weren't being linked, and this broke a bunch of tests. (The blob in repo5 that is being corrupted seems to be linked into repo5 in the loop running dumpflog.py prior to it being corrupted, but only if verify=False is handled too.) It's probably better to do a one time extra verification in order to create these files, so that the repo can be copied to a removable drive. Adding the same check to store.write() was only for completeness, but also needs to do a one time extra verification to avoid breaking tests. diff --git a/hgext/lfs/blobstore.py b/hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py +++ b/hgext/lfs/blobstore.py @@ -111,15 +111,23 @@ # XXX: should we verify the content of the cache, and hardlink back to # the local store on success, but truncate, write and link on failure? if not self.cachevfs.exists(oid): -self.ui.note(_('lfs: adding %s to the usercache\n') % oid) -lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) +if verify or hashlib.sha256(data).hexdigest() == oid: +self.ui.note(_('lfs: adding %s to the usercache\n') % oid) +lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) def read(self, oid, verify=True): """Read blob from local blobstore.""" if not self.vfs.exists(oid): blob = self._read(self.cachevfs, oid, verify) -self.ui.note(_('lfs: found %s in the usercache\n') % oid) -lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) + +# Even if revlog will verify the content, it needs to be verified +# now before making the hardlink to avoid propagating corrupt blobs. +# Don't abort if corruption is detected, because `hg verify` will +# give more useful info about the corruption- simply don't add the +# hardlink. +if verify or hashlib.sha256(blob).hexdigest() == oid: +self.ui.note(_('lfs: found %s in the usercache\n') % oid) +lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) else: self.ui.note(_('lfs: found %s in the local lfs store\n') % oid) blob = self._read(self.vfs, oid, verify) diff --git a/tests/test-lfs.t b/tests/test-lfs.t --- a/tests/test-lfs.t +++ b/tests/test-lfs.t @@ -658,8 +658,8 @@ lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store 4 files, 5 changesets, 10 total revisions -BUG: Verify will copy/link a corrupted file from the usercache into the local -store, and poison it. (The verify with a good remote now fails.) +Verify will not copy/link a corrupted file from the usercache into the local +store, and poison it. (The verify with a good remote now works.) $ rm -r fromcorrupt/.hg/store/lfs/objects/66/100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e $ hg -R fromcorrupt verify -v @@ -668,10 +668,8 @@ checking manifests crosschecking files in changesets and manifests checking files - lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0 lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store - lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store lfs: found b1a6ea88da0017a0e77db139a54618986e9a2489bee24af9fe596de9daac498c in the local lfs store @@ -685,17 +683,12 @@ checking manifests crosschecking files in changesets and manifests checking files - lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store - l@1: unpacking 46a2f24864bc: integrity check failed on data/l.i:0 + lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the usercache lfs: found 22f66a3fc0b9bf3f012c814303995ec07099b3a9ce02a7af84b5970811074a3b in the local lfs store lfs: found 66100b384bf761271b407d79fc30cdd0554f3b2c5d944836e936d584b88ce88e in the local lfs store - large@0: unpacking 2c531e0992ff: integrity check failed on data/large.i:0 lfs: found 89b6070915a3d573ff3599d1cda305bc5e38549b15c4847ab034169da66e1ca8 in the local lfs store lfs: found b1a6ea88da
[PATCH 5 of 7] lfs: narrow the exceptions that trigger a transfer retry
# HG changeset patch # User Matt Harbison # Date 1512441664 18000 # Mon Dec 04 21:41:04 2017 -0500 # Node ID 3ffb73f5ab3b12511008dc8496c913bd639f3f38 # Parent 51a98a20b44618eade6d9227cc8c5473b44450f5 lfs: narrow the exceptions that trigger a transfer retry The retries were added to workaround TCP RESETs in fb-experimental fc8c131314a9. I have no idea if that's been debugged yet, but this wide net caught local I/O errors, bad hostnames and other things that shouldn't be retried. The next patch will validate objects as they are uploaded, and there's no need to retry those errors. The spec[1] does mention that certain http errors can be retried, including 500. But let's work through the corruption detection issues first. [1] https://github.com/git-lfs/git-lfs/blob/master/docs/api/batch.md diff --git a/hgext/lfs/blobstore.py b/hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py +++ b/hgext/lfs/blobstore.py @@ -10,6 +10,7 @@ import json import os import re +import socket from mercurial.i18n import _ @@ -286,7 +287,7 @@ self._basictransfer(obj, action, localstore) yield 1, obj.get('oid') break -except Exception as ex: +except socket.error as ex: if retry > 0: if self.ui.verbose: self.ui.write( diff --git a/tests/test-lfs-test-server.t b/tests/test-lfs-test-server.t --- a/tests/test-lfs-test-server.t +++ b/tests/test-lfs-test-server.t @@ -146,12 +146,7 @@ pushing to ../repo1 searching for changes lfs: uploading e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0 (17 bytes) - lfs: failed: LfsRemoteError('HTTP error: HTTP Error 500: Internal Server Error (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 5) - lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 4) - lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 3) - lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 2) - lfs: failed: LfsRemoteError('HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)',) (remaining retry 1) - abort: HTTP error: HTTP Error 404: Not Found (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)! + abort: HTTP error: HTTP Error 500: Internal Server Error (oid=e659058e26b07b39d2a9c7145b3f99b41f797b6621c8076600e9cb7ee88291f0, action=upload)! [255] Check error message when the remote missed a blob: ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 2 of 7] tests: teach `f` to handle sha256 checksums
# HG changeset patch # User Matt Harbison # Date 1513820793 18000 # Wed Dec 20 20:46:33 2017 -0500 # Node ID 00f733da93f18119d262e988985661c55c1707c3 # Parent 8675cce538f01075e9ef13c521d5c9db3e59e6f3 tests: teach `f` to handle sha256 checksums diff --git a/tests/f b/tests/f --- a/tests/f +++ b/tests/f @@ -59,7 +59,7 @@ if isfile: if opts.type: facts.append('file') -if opts.hexdump or opts.dump or opts.md5 or opts.sha1: +if any((opts.hexdump, opts.dump, opts.md5, opts.sha1, opts.sha256)): content = open(f, 'rb').read() elif islink: if opts.type: @@ -95,6 +95,9 @@ if opts.sha1 and content is not None: h = hashlib.sha1(content) facts.append('sha1=%s' % h.hexdigest()[:opts.bytes]) +if opts.sha256 and content is not None: +h = hashlib.sha256(content) +facts.append('sha256=%s' % h.hexdigest()[:opts.bytes]) if isstdin: outfile.write(b', '.join(facts) + b'\n') elif facts: @@ -150,6 +153,8 @@ help="recurse into directories") parser.add_option("-S", "--sha1", action="store_true", help="show sha1 hash of the content") +parser.add_option("", "--sha256", action="store_true", + help="show sha256 hash of the content") parser.add_option("-M", "--md5", action="store_true", help="show md5 hash of the content") parser.add_option("-D", "--dump", action="store_true", diff --git a/tests/test-tools.t b/tests/test-tools.t --- a/tests/test-tools.t +++ b/tests/test-tools.t @@ -13,6 +13,7 @@ check if file is newer (or same) -r, --recurse recurse into directories -S, --sha1show sha1 hash of the content +--sha256 show sha256 hash of the content -M, --md5 show md5 hash of the content -D, --dumpdump file content -H, --hexdump hexdump file content @@ -41,6 +42,9 @@ $ f --sha1 foo foo: sha1=f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 + $ f --sha256 foo + foo: sha256=b5bb9d8014a0f9b1d61e21e796d78dccdf1352f23cd32812f4850b878ae4944c + #if symlink $ f foo --mode foo: mode=644 ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 3 of 7] lfs: add note messages indicating what store holds the lfs blob
# HG changeset patch # User Matt Harbison # Date 1513724024 18000 # Tue Dec 19 17:53:44 2017 -0500 # Node ID 1a68987ba0c3fa2494a8fae8321cddd0876efd7f # Parent 00f733da93f18119d262e988985661c55c1707c3 lfs: add note messages indicating what store holds the lfs blob The following corruption related patches were written prior to adding the user level cache, and it took awhile to track down why the tests changed. (It generally made things more resilient.) But I think this will be useful to the end user as well. I didn't make it --debug level, because there can be a ton of info coming out of clone/push/pull --debug. The pointers are sorted for test stability. I opted for ui.note() instead of checking ui.verbose and then using ui.write() for convenience, but I see most of this extension does the latter. I have no idea what the preferred form is. diff --git a/hgext/lfs/blobstore.py b/hgext/lfs/blobstore.py --- a/hgext/lfs/blobstore.py +++ b/hgext/lfs/blobstore.py @@ -96,6 +96,7 @@ self.vfs = lfsvfs(fullpath) usercache = lfutil._usercachedir(repo.ui, 'lfs') self.cachevfs = lfsvfs(usercache) +self.ui = repo.ui def write(self, oid, data): """Write blob to local blobstore.""" @@ -105,12 +106,16 @@ # XXX: should we verify the content of the cache, and hardlink back to # the local store on success, but truncate, write and link on failure? if not self.cachevfs.exists(oid): +self.ui.note(_('lfs: adding %s to the usercache\n') % oid) lfutil.link(self.vfs.join(oid), self.cachevfs.join(oid)) def read(self, oid): """Read blob from local blobstore.""" if not self.vfs.exists(oid): lfutil.link(self.cachevfs.join(oid), self.vfs.join(oid)) +self.ui.note(_('lfs: found %s in the usercache\n') % oid) +else: +self.ui.note(_('lfs: found %s in the local lfs store\n') % oid) return self.vfs.read(oid) def has(self, oid): diff --git a/hgext/lfs/wrapper.py b/hgext/lfs/wrapper.py --- a/hgext/lfs/wrapper.py +++ b/hgext/lfs/wrapper.py @@ -279,7 +279,7 @@ ctx = repo[r] for p in pointersfromctx(ctx).values(): pointers[p.oid()] = p -return pointers.values() +return sorted(pointers.values()) def pointersfromctx(ctx): """return a dict {path: pointer} for given single changectx""" diff --git a/tests/test-lfs-test-server.t b/tests/test-lfs-test-server.t --- a/tests/test-lfs-test-server.t +++ b/tests/test-lfs-test-server.t @@ -61,7 +61,9 @@ resolving manifests getting a lfs: downloading 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b (12 bytes) + lfs: adding 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b to the usercache lfs: processed: 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store 1 files updated, 0 files merged, 0 files removed, 0 files unresolved When the server has some blobs already @@ -90,12 +92,17 @@ $ hg --repo ../repo1 update tip -v resolving manifests getting b + lfs: found 31cf46fbc4ecd458a0943c5b4881f1f5a6dd36c53d6167d5b69ac45149b38e5b in the local lfs store getting c lfs: downloading d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 (19 bytes) + lfs: adding d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 to the usercache lfs: processed: d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 + lfs: found d11e1a642b60813aee592094109b406089b8dff4cb157157f753418ec7857998 in the local lfs store getting d lfs: downloading 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 (20 bytes) + lfs: adding 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 to the usercache lfs: processed: 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 + lfs: found 37a65ab78d5ecda767e8622c248b5dbff1e68b1678ab0e730d5eb8601ec8ad19 in the local lfs store 3 files updated, 0 files merged, 0 files removed, 0 files unresolved Check error message when the remote missed a blob: diff --git a/tests/test-lfs.t b/tests/test-lfs.t --- a/tests/test-lfs.t +++ b/tests/test-lfs.t @@ -59,6 +59,7 @@ $ hg push -v | egrep -v '^(uncompressed| )' pushing to $TESTTMP/server searching for changes + lfs: found f11e77c257047a398492d8d6cb9f6acf3aa7c4384bb23080b43546053e183e4b in the local lfs store 2 changesets found adding changesets adding manifests @@ -192,6 +193,7 @@ $ echo SMALL > small $ hg commit -Aqm 'create a lfs file' large small $ hg debuglfsupload -r 'all()' -v + lfs: found 8e92251415339ae9b148c8da89ed5ec665905166a1ab11b09dca8fad83344738 in the local lfs store $ cd .. @@ -316,6 +318,10 @@ $ hg commit -m branching -q $ hg bundle --base 1 bundle.hg -v + lfs: found 5ab7a3739a5feec94a562d070a
[PATCH 1 of 7] tests: fix a bug in `f` that prevented calculating sha1sum on a file
# HG changeset patch # User Matt Harbison # Date 1513820472 18000 # Wed Dec 20 20:41:12 2017 -0500 # Node ID 8675cce538f01075e9ef13c521d5c9db3e59e6f3 # Parent 44fd4cfc6c0ad3107cacad10c76ed38bd74948f4 tests: fix a bug in `f` that prevented calculating sha1sum on a file diff --git a/tests/f b/tests/f --- a/tests/f +++ b/tests/f @@ -59,7 +59,7 @@ if isfile: if opts.type: facts.append('file') -if opts.hexdump or opts.dump or opts.md5: +if opts.hexdump or opts.dump or opts.md5 or opts.sha1: content = open(f, 'rb').read() elif islink: if opts.type: diff --git a/tests/test-tools.t b/tests/test-tools.t --- a/tests/test-tools.t +++ b/tests/test-tools.t @@ -38,6 +38,9 @@ $ f foo foo: + $ f --sha1 foo + foo: sha1=f1d2d2f924e986ac86fdf7b36c94bcdf32beec15 + #if symlink $ f foo --mode foo: mode=644 ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1739: filemerge: only raise InMemoryMergeConflictsError when running _xmerge
This revision was automatically updated to reflect the committed changes. Closed by commit rHGef7e667a4f7b: filemerge: only raise InMemoryMergeConflictsError when running _xmerge (authored by phillco, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1739?vs=4567&id=4572 REVISION DETAIL https://phab.mercurial-scm.org/D1739 AFFECTED FILES mercurial/filemerge.py CHANGE DETAILS diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py --- a/mercurial/filemerge.py +++ b/mercurial/filemerge.py @@ -490,6 +490,18 @@ return _idump(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=labels) +def _xmergeimm(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): +# In-memory merge simply raises an exception on all external merge tools, +# for now. +# +# It would be possible to run most tools with temporary files, but this +# raises the question of what to do if the user only partially resolves the +# file -- we can't leave a merge state. (Copy to somewhere in the .hg/ +# directory and tell the user how to get it is my best idea, but it's +# clunky.) +raise error.InMemoryMergeConflictsError('in-memory merge does not support ' +'external merge tools') + def _xmerge(repo, mynode, orig, fcd, fco, fca, toolconf, files, labels=None): tool, toolpath, binary, symlink = toolconf if fcd.isabsent() or fco.isabsent(): @@ -688,16 +700,14 @@ onfailure = func.onfailure precheck = func.precheck else: -func = _xmerge +if wctx.isinmemory(): +func = _xmergeimm +else: +func = _xmerge mergetype = fullmerge onfailure = _("merging %s failed!\n") precheck = None -if wctx.isinmemory(): -raise error.InMemoryMergeConflictsError('in-memory merge does not ' -'support external merge ' -'tools') - toolconf = tool, toolpath, binary, symlink if mergetype == nomerge: To: phillco, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1740: journal: use pager
This revision was automatically updated to reflect the committed changes. Closed by commit rHG6f754b0fe54e: journal: use pager (authored by quark, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1740?vs=4568&id=4571 REVISION DETAIL https://phab.mercurial-scm.org/D1740 AFFECTED FILES hgext/journal.py CHANGE DETAILS diff --git a/hgext/journal.py b/hgext/journal.py --- a/hgext/journal.py +++ b/hgext/journal.py @@ -480,6 +480,7 @@ limit = cmdutil.loglimit(opts) entry = None +ui.pager('journal') for count, entry in enumerate(repo.journal.filtered(name=name)): if count == limit: break To: quark, #hg-reviewers, durin42 Cc: mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
Re: [PATCH] paper: minor adjustments to table styles
> On Dec 21, 2017, at 9:45 AM, Anton Shestakov wrote: > > # HG changeset patch > # User Anton Shestakov > # Date 1513863320 -28800 > # Thu Dec 21 21:35:20 2017 +0800 > # Node ID cdf339ec88f4264c40d3613912f49988001ebc32 > # Parent 4273260ac0d6e9bcb293607c7bdc16d0d246e6e9 > # EXP-Topic hgweb-more-info > paper: minor adjustments to table styles queued, thanks ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 3 of 4] templater: move repo, ui and cache to per-engine resources
# HG changeset patch # User Yuya Nishihara # Date 1513861530 -32400 # Thu Dec 21 22:05:30 2017 +0900 # Node ID c06827f22f361f8e1ff3431c18ed0734708149ed # Parent 420618063c2cf0ab50e06e075b2feb49eef77767 templater: move repo, ui and cache to per-engine resources diff --git a/hgext/show.py b/hgext/show.py --- a/hgext/show.py +++ b/hgext/show.py @@ -252,7 +252,9 @@ def showstack(ui, repo, displayer): # our simplicity and the customizations required. # TODO use proper graph symbols from graphmod -shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen) +tres = formatter.templateresources(ui, repo) +shortesttmpl = formatter.maketemplater(ui, '{shortest(node, %d)}' % nodelen, + resources=tres) def shortest(ctx): return shortesttmpl.render({'ctx': ctx, 'node': ctx.hex()}) @@ -438,7 +440,10 @@ def longestshortest(repo, revs, minlen=4 If we fail to do this, a value of e.g. ``10023`` could mean either revision 10023 or node ``10023abc...``. """ -tmpl = formatter.maketemplater(repo.ui, '{shortest(node, %d)}' % minlen) +tres = formatter.templateresources(repo.ui, repo) +tmpl = formatter.maketemplater(repo.ui, '{shortest(node, %d)}' % minlen, + resources=tres) + lens = [minlen] for rev in revs: ctx = repo[rev] diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -1843,10 +1843,11 @@ class changeset_templater(changeset_prin diffopts = diffopts or {} changeset_printer.__init__(self, ui, repo, matchfn, diffopts, buffered) -self.t = formatter.loadtemplater(ui, tmplspec, +tres = formatter.templateresources(ui, repo) +self.t = formatter.loadtemplater(ui, tmplspec, resources=tres, cache=templatekw.defaulttempl) self._counter = itertools.count() -self.cache = {} +self.cache = tres['cache'] # shared with _graphnodeformatter() self._tref = tmplspec.ref self._parts = {'header': '', 'footer': '', @@ -1887,11 +1888,8 @@ class changeset_templater(changeset_prin props = props.copy() props.update(templatekw.keywords) props['ctx'] = ctx -props['repo'] = self.repo -props['ui'] = self.repo.ui props['index'] = index = next(self._counter) props['revcache'] = {'copies': copies} -props['cache'] = self.cache props = pycompat.strkwargs(props) # write separator, which wouldn't work well with the header part below @@ -2657,16 +2655,14 @@ def _graphnodeformatter(ui, displayer): return templatekw.showgraphnode # fast path for "{graphnode}" spec = templater.unquotestring(spec) -templ = formatter.maketemplater(ui, spec) -cache = {} +tres = formatter.templateresources(ui) if isinstance(displayer, changeset_templater): -cache = displayer.cache # reuse cache of slow templates +tres['cache'] = displayer.cache # reuse cache of slow templates +templ = formatter.maketemplater(ui, spec, resources=tres) props = templatekw.keywords.copy() -props['cache'] = cache def formatnode(repo, ctx): props['ctx'] = ctx props['repo'] = repo -props['ui'] = repo.ui props['revcache'] = {} return templ.render(props) return formatnode diff --git a/mercurial/debugcommands.py b/mercurial/debugcommands.py --- a/mercurial/debugcommands.py +++ b/mercurial/debugcommands.py @@ -2365,8 +2365,8 @@ def debugtemplate(ui, repo, tmpl, **opts ui.note(("* expanded:\n"), templater.prettyformat(newtree), '\n') if revs is None: -t = formatter.maketemplater(ui, tmpl) -props['ui'] = ui +tres = formatter.templateresources(ui, repo) +t = formatter.maketemplater(ui, tmpl, resources=tres) ui.write(t.render(props)) else: displayer = cmdutil.makelogtemplater(ui, repo, tmpl) diff --git a/mercurial/filemerge.py b/mercurial/filemerge.py --- a/mercurial/filemerge.py +++ b/mercurial/filemerge.py @@ -569,7 +569,8 @@ def _formatlabels(repo, fcd, fco, fca, l ui = repo.ui template = ui.config('ui', 'mergemarkertemplate') template = templater.unquotestring(template) -tmpl = formatter.maketemplater(ui, template) +tres = formatter.templateresources(ui, repo) +tmpl = formatter.maketemplater(ui, template, resources=tres) pad = max(len(l) for l in labels) diff --git a/mercurial/formatter.py b/mercurial/formatter.py --- a/mercurial/formatter.py +++ b/mercurial/formatter.py @@ -363,11 +363,11 @@ class templateformatter(baseformatter): self._out = out spec = lookuptemplate(ui, topic, opts.get('template', '')) self._tref = spec.ref -self._t = loadtemplater(ui, spec, cache=templatekw.defaulttempl)
[PATCH 1 of 4] templater: look up mapping table through template engine
# HG changeset patch # User Yuya Nishihara # Date 1513857805 -32400 # Thu Dec 21 21:03:25 2017 +0900 # Node ID 692f9bbcae0853b547ab0c2f2d9d0b93ede85ab0 # Parent b520c8f98e1e47dd221df9cfeeaaa2d19c6dc4e9 templater: look up mapping table through template engine These functions are stub for symbol/resource separation. This series is intended to address the following problems: a) internal data may be exposed to user (issue5699) b) defaults['repo'] (a repository name) will conflict with mapping['repo'] (a repo object) in hgweb diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -382,9 +382,7 @@ def _runrecursivesymbol(context, mapping raise error.Abort(_("recursive reference '%s' in template") % key) def runsymbol(context, mapping, key, default=''): -v = mapping.get(key) -if v is None: -v = context._defaults.get(key) +v = context.symbol(mapping, key) if v is None: # put poison to cut recursion. we can't move this to parsing phase # because "x = {x}" is allowed if "x" is a keyword. (issue4758) @@ -626,7 +624,7 @@ def diff(context, mapping, args): return [s] return [] -ctx = mapping['ctx'] +ctx = context.resource(mapping, 'ctx') chunks = ctx.diff(match=ctx.match([], getpatterns(0), getpatterns(1))) return ''.join(chunks) @@ -639,8 +637,8 @@ def extdata(context, mapping, args): raise error.ParseError(_('extdata expects one argument')) source = evalstring(context, mapping, args['source']) -cache = mapping['cache'].setdefault('extdata', {}) -ctx = mapping['ctx'] +cache = context.resource(mapping, 'cache').setdefault('extdata', {}) +ctx = context.resource(mapping, 'ctx') if source in cache: data = cache[source] else: @@ -656,7 +654,7 @@ def files(context, mapping, args): raise error.ParseError(_("files expects one argument")) raw = evalstring(context, mapping, args[0]) -ctx = mapping['ctx'] +ctx = context.resource(mapping, 'ctx') m = ctx.match([raw]) files = list(ctx.matches(m)) return templatekw.showlist("file", files, mapping) @@ -692,7 +690,7 @@ def formatnode(context, mapping, args): # i18n: "formatnode" is a keyword raise error.ParseError(_("formatnode expects one argument")) -ui = mapping['ui'] +ui = context.resource(mapping, 'ui') node = evalstring(context, mapping, args[0]) if ui.debugflag: return node @@ -858,7 +856,7 @@ def label(context, mapping, args): # i18n: "label" is a keyword raise error.ParseError(_("label expects two arguments")) -ui = mapping['ui'] +ui = context.resource(mapping, 'ui') thing = evalstring(context, mapping, args[1]) # preserve unknown symbol as literal so effects like 'red', 'bold', # etc. don't need to be quoted @@ -1030,7 +1028,7 @@ def relpath(context, mapping, args): # i18n: "relpath" is a keyword raise error.ParseError(_("relpath expects one argument")) -repo = mapping['ctx'].repo() +repo = context.resource(mapping, 'ctx').repo() path = evalstring(context, mapping, args[0]) return repo.pathto(path) @@ -1043,7 +1041,7 @@ def revset(context, mapping, args): raise error.ParseError(_("revset expects one or more arguments")) raw = evalstring(context, mapping, args[0]) -ctx = mapping['ctx'] +ctx = context.resource(mapping, 'ctx') repo = ctx.repo() def query(expr): @@ -1055,7 +1053,8 @@ def revset(context, mapping, args): revs = query(revsetlang.formatspec(raw, *formatargs)) revs = list(revs) else: -revsetcache = mapping['cache'].setdefault("revsetcache", {}) +cache = context.resource(mapping, 'cache') +revsetcache = cache.setdefault("revsetcache", {}) if raw in revsetcache: revs = revsetcache[raw] else: @@ -1116,7 +1115,7 @@ def shortest(context, mapping, args): # _partialmatch() of filtered changelog could take O(len(repo)) time, # which would be unacceptably slow. so we look for hash collision in # unfiltered space, which means some hashes may be slightly longer. -cl = mapping['ctx']._repo.unfiltered().changelog +cl = context.resource(mapping, 'ctx')._repo.unfiltered().changelog return cl.shortest(node, minlength) @templatefunc('strip(text[, chars])') @@ -1302,6 +1301,18 @@ class engine(object): self._aliasmap = _aliasrules.buildmap(aliases) self._cache = {} # key: (func, data) +def symbol(self, mapping, key): +"""Resolve symbol to value or function; None if nothing found""" +v = mapping.get(key) +if v is None: +v = self._defaults.get(key) +return v + +def resource(self, mapping, key): +"""Return internal data (e.g. cache) used for keyword/function +evalua
[PATCH 4 of 4] templater: look up symbols/resources as if they were separated (issue5699)
# HG changeset patch # User Yuya Nishihara # Date 1513862259 -32400 # Thu Dec 21 22:17:39 2017 +0900 # Node ID 9d50cfedc380d05dfbe0ed8b9360603b61b3c601 # Parent c06827f22f361f8e1ff3431c18ed0734708149ed templater: look up symbols/resources as if they were separated (issue5699) It wouldn't be easy to split the mapping dict into (symbols, resources). This patch instead rejects invalid lookup taking resources.keys() as source of truth. The doctest is updated since mapping['repo'] is now reserved for a repo object. diff --git a/mercurial/formatter.py b/mercurial/formatter.py --- a/mercurial/formatter.py +++ b/mercurial/formatter.py @@ -94,14 +94,14 @@ Nested example: >>> def subrepos(ui, fm): ... fm.startitem() -... fm.write(b'repo', b'[%s]\\n', b'baz') +... fm.write(b'reponame', b'[%s]\\n', b'baz') ... files(ui, fm.nested(b'files')) ... fm.end() >>> show(subrepos) [baz] foo bar ->>> show(subrepos, template=b'{repo}: {join(files % "{path}", ", ")}\\n') +>>> show(subrepos, template=b'{reponame}: {join(files % "{path}", ", ")}\\n') baz: foo, bar """ @@ -491,7 +491,9 @@ def templateresources(ui, repo=None): and function""" return { 'cache': {}, # for templatekw/funcs to store reusable data +'ctx': None, 'repo': repo, +'revcache': None, # per-ctx cache; set later 'ui': ui, } diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -1320,7 +1320,9 @@ class engine(object): def symbol(self, mapping, key): """Resolve symbol to value or function; None if nothing found""" -v = mapping.get(key) +v = None +if key not in self._resources: +v = mapping.get(key) if v is None: v = self._defaults.get(key) return v @@ -1328,11 +1330,13 @@ class engine(object): def resource(self, mapping, key): """Return internal data (e.g. cache) used for keyword/function evaluation""" -v = mapping.get(key) +v = None +if key in self._resources: +v = mapping.get(key) if v is None: v = self._resources.get(key) if v is None: -raise KeyError +raise error.Abort(_('template resource not available: %s') % key) return v def _load(self, t): diff --git a/tests/test-command-template.t b/tests/test-command-template.t --- a/tests/test-command-template.t +++ b/tests/test-command-template.t @@ -206,7 +206,13 @@ never cause crash: Internal resources shouldn't be exposed (issue5699): - $ hg log -r. -T '{cache}{repo}{templ}{ui}' + $ hg log -r. -T '{cache}{ctx}{repo}{revcache}{templ}{ui}' + +Never crash on internal resource not available: + + $ hg --cwd .. debugtemplate '{"c0bebeef"|shortest}\n' + abort: template resource not available: ctx + [255] Quoting for ui.logtemplate ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH 2 of 4] templater: keep default resources per template engine (API)
# HG changeset patch # User Yuya Nishihara # Date 1513859346 -32400 # Thu Dec 21 21:29:06 2017 +0900 # Node ID 420618063c2cf0ab50e06e075b2feb49eef77767 # Parent 692f9bbcae0853b547ab0c2f2d9d0b93ede85ab0 templater: keep default resources per template engine (API) This allows us to register a repo object as a resource in hgweb template, without loosing '{repo}' symbol: symbol('repo') -> mapping['repo'] (n/a) -> defaults['repo'] resource('repo') -> mapping['repo'] (n/a) -> resources['repo'] I'm thinking of redesigning the templatekw API to take (context, mapping) in place of **(context._resources + mapping), but that will be a big change and not implemented yet. props['templ'] is ported to the resources dict as an example. .. api:: mapping does not contain all template resources. use context.resource() in template functions. diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -1886,7 +1886,6 @@ class changeset_templater(changeset_prin '''show a single changeset or file revision''' props = props.copy() props.update(templatekw.keywords) -props['templ'] = self.t props['ctx'] = ctx props['repo'] = self.repo props['ui'] = self.repo.ui @@ -2663,7 +2662,6 @@ def _graphnodeformatter(ui, displayer): if isinstance(displayer, changeset_templater): cache = displayer.cache # reuse cache of slow templates props = templatekw.keywords.copy() -props['templ'] = templ props['cache'] = cache def formatnode(repo, ctx): props['ctx'] = ctx diff --git a/mercurial/formatter.py b/mercurial/formatter.py --- a/mercurial/formatter.py +++ b/mercurial/formatter.py @@ -392,7 +392,6 @@ class templateformatter(baseformatter): props.update(item) if 'ctx' in item: # but template resources must be always available -props['templ'] = self._t props['repo'] = props['ctx'].repo() props['revcache'] = {} props = pycompat.strkwargs(props) @@ -468,18 +467,19 @@ def templatepartsmap(spec, t, partnames) partsmap[part] = ref return partsmap -def loadtemplater(ui, spec, cache=None): +def loadtemplater(ui, spec, resources=None, cache=None): """Create a templater from either a literal template or loading from a map file""" assert not (spec.tmpl and spec.mapfile) if spec.mapfile: -return templater.templater.frommapfile(spec.mapfile, cache=cache) -return maketemplater(ui, spec.tmpl, cache=cache) +frommapfile = templater.templater.frommapfile +return frommapfile(spec.mapfile, resources=resources, cache=cache) +return maketemplater(ui, spec.tmpl, resources=resources, cache=cache) -def maketemplater(ui, tmpl, cache=None): +def maketemplater(ui, tmpl, resources=None, cache=None): """Create a templater from a string template 'tmpl'""" aliases = ui.configitems('templatealias') -t = templater.templater(cache=cache, aliases=aliases) +t = templater.templater(resources=resources, cache=cache, aliases=aliases) t.cache.update((k, templater.unquotestring(v)) for k, v in ui.configitems('templates')) if tmpl: diff --git a/mercurial/templater.py b/mercurial/templater.py --- a/mercurial/templater.py +++ b/mercurial/templater.py @@ -393,7 +393,11 @@ def runsymbol(context, mapping, key, def except TemplateNotFound: v = default if callable(v): -return v(**pycompat.strkwargs(mapping)) +# TODO: templatekw functions will be updated to take (context, mapping) +# pair instead of **props +props = context._resources.copy() +props.update(mapping) +return v(**props) return v def buildtemplate(exp, context): @@ -657,7 +661,10 @@ def files(context, mapping, args): ctx = context.resource(mapping, 'ctx') m = ctx.match([raw]) files = list(ctx.matches(m)) -return templatekw.showlist("file", files, mapping) +# TODO: pass (context, mapping) pair to keyword function +props = context._resources.copy() +props.update(mapping) +return templatekw.showlist("file", files, props) @templatefunc('fill(text[, width[, initialident[, hangindent]]])') def fill(context, mapping, args): @@ -878,7 +885,10 @@ def latesttag(context, mapping, args): if len(args) == 1: pattern = evalstring(context, mapping, args[0]) -return templatekw.showlatesttags(pattern, **pycompat.strkwargs(mapping)) +# TODO: pass (context, mapping) pair to keyword function +props = context._resources.copy() +props.update(mapping) +return templatekw.showlatesttags(pattern, **pycompat.strkwargs(props)) @templatefunc('localdate(date[, tz])') def localdate(context, mapping, args): @@ -1062,8 +1072,11 @@ def revset(context, mapping, args): revs = list(revs) revsetcache[raw] = revs
D1736: osutil: add a function to unblock signals
This revision was automatically updated to reflect the committed changes. Closed by commit rHG8652ab4046e4: osutil: add a function to unblock signals (authored by quark, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1736?vs=4561&id=4569 REVISION DETAIL https://phab.mercurial-scm.org/D1736 AFFECTED FILES mercurial/cext/osutil.c mercurial/policy.py mercurial/util.py CHANGE DETAILS diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -163,6 +163,10 @@ setprocname = osutil.setprocname except AttributeError: pass +try: +unblocksignal = osutil.unblocksignal +except AttributeError: +pass # Python compatibility diff --git a/mercurial/policy.py b/mercurial/policy.py --- a/mercurial/policy.py +++ b/mercurial/policy.py @@ -74,7 +74,7 @@ (r'cext', r'bdiff'): 1, (r'cext', r'diffhelpers'): 1, (r'cext', r'mpatch'): 1, -(r'cext', r'osutil'): 1, +(r'cext', r'osutil'): 2, (r'cext', r'parsers'): 4, } diff --git a/mercurial/cext/osutil.c b/mercurial/cext/osutil.c --- a/mercurial/cext/osutil.c +++ b/mercurial/cext/osutil.c @@ -20,6 +20,7 @@ #include #else #include +#include #include #include #include @@ -,6 +1112,25 @@ } #endif /* defined(HAVE_LINUX_STATFS) || defined(HAVE_BSD_STATFS) */ +static PyObject *unblocksignal(PyObject *self, PyObject *args) +{ + int sig = 0; + int r; + if (!PyArg_ParseTuple(args, "i", &sig)) + return NULL; + sigset_t set; + r = sigemptyset(&set); + if (r != 0) + return PyErr_SetFromErrno(PyExc_OSError); + r = sigaddset(&set, sig); + if (r != 0) + return PyErr_SetFromErrno(PyExc_OSError); + r = sigprocmask(SIG_UNBLOCK, &set, NULL); + if (r != 0) + return PyErr_SetFromErrno(PyExc_OSError); + Py_RETURN_NONE; +} + #endif /* ndef _WIN32 */ static PyObject *listdir(PyObject *self, PyObject *args, PyObject *kwargs) @@ -1291,6 +1311,8 @@ {"getfstype", (PyCFunction)getfstype, METH_VARARGS, "get filesystem type (best-effort)\n"}, #endif + {"unblocksignal", (PyCFunction)unblocksignal, METH_VARARGS, +"change signal mask to unblock a given signal\n"}, #endif /* ndef _WIN32 */ #ifdef __APPLE__ { @@ -1301,7 +1323,7 @@ {NULL, NULL} }; -static const int version = 1; +static const int version = 2; #ifdef IS_PY3K static struct PyModuleDef osutil_module = { To: quark, #hg-reviewers, yuja Cc: yuja, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
D1737: commandserver: unblock SIGCHLD
This revision was automatically updated to reflect the committed changes. Closed by commit rHG3a119a423953: commandserver: unblock SIGCHLD (authored by quark, committed by ). REPOSITORY rHG Mercurial CHANGES SINCE LAST UPDATE https://phab.mercurial-scm.org/D1737?vs=4562&id=4570 REVISION DETAIL https://phab.mercurial-scm.org/D1737 AFFECTED FILES mercurial/commandserver.py CHANGE DETAILS diff --git a/mercurial/commandserver.py b/mercurial/commandserver.py --- a/mercurial/commandserver.py +++ b/mercurial/commandserver.py @@ -449,6 +449,8 @@ def init(self): self._sock = socket.socket(socket.AF_UNIX) self._servicehandler.bindsocket(self._sock, self.address) +if util.safehasattr(util, 'unblocksignal'): +util.unblocksignal(signal.SIGCHLD) o = signal.signal(signal.SIGCHLD, self._sigchldhandler) self._oldsigchldhandler = o self._socketunlinked = False To: quark, #hg-reviewers, yuja Cc: yuja, mercurial-devel ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel
[PATCH] paper: minor adjustments to table styles
# HG changeset patch # User Anton Shestakov # Date 1513863320 -28800 # Thu Dec 21 21:35:20 2017 +0800 # Node ID cdf339ec88f4264c40d3613912f49988001ebc32 # Parent 4273260ac0d6e9bcb293607c7bdc16d0d246e6e9 # EXP-Topic hgweb-more-info paper: minor adjustments to table styles Adding a bit of padding to table columns on e.g. /log means content and headers are better aligned: headers already have this padding. Right margin is removed from #changesetEntry th because elements with display: table-cell (such as ) ignore margins anyway. diff --git a/mercurial/templates/static/style-paper.css b/mercurial/templates/static/style-paper.css --- a/mercurial/templates/static/style-paper.css +++ b/mercurial/templates/static/style-paper.css @@ -213,6 +213,7 @@ h3 { } .bigtable td { + padding: 1px 4px; vertical-align: top; } @@ -431,7 +432,6 @@ span.followlines-select .btn-followlines text-align: right; font-weight: normal; color: #999; - margin-right: .5em; vertical-align: top; } ___ Mercurial-devel mailing list Mercurial-devel@mercurial-scm.org https://www.mercurial-scm.org/mailman/listinfo/mercurial-devel