# HG changeset patch # User David Soria Parra # Date 2011-02-11 19:35:32 # Node ID a184dbd9b2c52c2414ea247d6d5a9dacdc1dcd2d # Parent 900a92862a7b15dc5d8d694f56dcfc380002cbb6 # Parent 0be2fe6a0843183792349fa22c57e29a93febe1d localrepo: sort hg bookmark output sort bookmarks before we write them to stdout to get a predictable output. diff --git a/contrib/hgk b/contrib/hgk --- a/contrib/hgk +++ b/contrib/hgk @@ -482,7 +482,7 @@ proc makewindow {} { .bar.file add command -label "Quit" -command doquit menu .bar.help .bar add cascade -label "Help" -menu .bar.help - .bar.help add command -label "About gitk" -command about + .bar.help add command -label "About hgk" -command about . configure -menu .bar if {![info exists geometry(canv1)]} { @@ -867,9 +867,9 @@ proc about {} { return } toplevel $w - wm title $w "About gitk" + wm title $w "About hgk" message $w.m -text { -Gitk version 1.2 +Hgk version 1.2 Copyright � 2005 Paul Mackerras diff --git a/contrib/win32/buildlocal.bat b/contrib/win32/buildlocal.bat new file mode 100644 --- /dev/null +++ b/contrib/win32/buildlocal.bat @@ -0,0 +1,9 @@ +@echo off +rem Double-click this file to (re)build Mercurial for Windows in place. +rem Useful for testing and development. +cd ..\.. +del /Q mercurial\*.pyd +del /Q mercurial\*.pyc +rmdir /Q /S mercurial\locale +python setup.py build_py -c -d . build_ext -i build_mo +pause diff --git a/contrib/wix/dist.wxs b/contrib/wix/dist.wxs --- a/contrib/wix/dist.wxs +++ b/contrib/wix/dist.wxs @@ -16,23 +16,14 @@ - - - - - - - - - + - diff --git a/contrib/wix/guids.wxi b/contrib/wix/guids.wxi --- a/contrib/wix/guids.wxi +++ b/contrib/wix/guids.wxi @@ -9,7 +9,7 @@ - + diff --git a/mercurial/bookmarks.py b/mercurial/bookmarks.py --- a/mercurial/bookmarks.py +++ b/mercurial/bookmarks.py @@ -38,7 +38,7 @@ def readcurrent(repo): if os.path.exists(repo.join('bookmarks.current')): file = repo.opener('bookmarks.current') # No readline() in posixfile_nt, reading everything is cheap - mark = (file.readlines() or [''])[0] + mark = encoding.tolocal((file.readlines() or [''])[0]) if mark == '': mark = None file.close() diff --git a/mercurial/bundlerepo.py b/mercurial/bundlerepo.py --- a/mercurial/bundlerepo.py +++ b/mercurial/bundlerepo.py @@ -251,11 +251,6 @@ class bundlerepository(localrepo.localre self.bundle.close() if self.tempfile is not None: os.unlink(self.tempfile) - - def __del__(self): - del self.bundle - if self.tempfile is not None: - os.unlink(self.tempfile) if self._tempparent: shutil.rmtree(self._tempparent, True) diff --git a/mercurial/cmdutil.py b/mercurial/cmdutil.py --- a/mercurial/cmdutil.py +++ b/mercurial/cmdutil.py @@ -806,6 +806,9 @@ class changeset_printer(object): if branch != 'default': self.ui.write(_("branch: %s\n") % branch, label='log.branch') + for bookmark in self.repo.nodebookmarks(changenode): + self.ui.write(_("bookmark: %s\n") % bookmark, + label='log.bookmark') for tag in self.repo.nodetags(changenode): self.ui.write(_("tag: %s\n") % tag, label='log.tag') diff --git a/mercurial/commands.py b/mercurial/commands.py --- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -530,7 +530,7 @@ def bookmark(ui, repo, mark=None, rev=No if len(marks) == 0: ui.status(_("no bookmarks set\n")) else: - for bmark, n in marks.iteritems(): + for bmark, n in sorted(marks.iteritems()): if ui.configbool('bookmarks', 'track.current'): current = repo._bookmarkcurrent if bmark == current and n == cur: diff --git a/mercurial/context.py b/mercurial/context.py --- a/mercurial/context.py +++ b/mercurial/context.py @@ -114,6 +114,8 @@ class changectx(object): return self._changeset[5] def tags(self): return self._repo.nodetags(self._node) + def bookmarks(self): + return self._repo.nodebookmarks(self._node) def parents(self): """return contexts for each parent changeset""" diff --git a/mercurial/dispatch.py b/mercurial/dispatch.py --- a/mercurial/dispatch.py +++ b/mercurial/dispatch.py @@ -589,8 +589,12 @@ def _dispatch(ui, args): msg = ' '.join(' ' in a and repr(a) or a for a in fullargs) ui.log("command", msg + "\n") d = lambda: util.checksignature(func)(ui, *args, **cmdoptions) - return runcommand(lui, repo, cmd, fullargs, ui, options, d, - cmdpats, cmdoptions) + try: + return runcommand(lui, repo, cmd, fullargs, ui, options, d, + cmdpats, cmdoptions) + finally: + if repo: + repo.close() def _runcommand(ui, options, cmd, cmdfunc): def checkargs(): diff --git a/mercurial/localrepo.py b/mercurial/localrepo.py --- a/mercurial/localrepo.py +++ b/mercurial/localrepo.py @@ -360,7 +360,6 @@ class localrepository(repo.repository): if node != nullid: tags[encoding.tolocal(name)] = node tags['tip'] = self.changelog.tip() - tags.update(self._bookmarks) tagtypes = dict([(encoding.tolocal(name), value) for (name, value) in tagtypes.iteritems()]) return (tags, tagtypes) @@ -399,6 +398,13 @@ class localrepository(repo.repository): tags.sort() return self.nodetagscache.get(node, []) + def nodebookmarks(self, node): + marks = [] + for bookmark, n in self._bookmarks.iteritems(): + if n == node: + marks.append(bookmark) + return sorted(marks) + def _branchtags(self, partial, lrev): # TODO: rename this function? tiprev = len(self) - 1 diff --git a/mercurial/posix.py b/mercurial/posix.py --- a/mercurial/posix.py +++ b/mercurial/posix.py @@ -13,6 +13,7 @@ posixfile = open nulldev = '/dev/null' normpath = os.path.normpath samestat = os.path.samestat +os_link = os.link unlink = os.unlink rename = os.rename expandglobs = False @@ -24,6 +25,10 @@ def openhardlinks(): '''return true if it is safe to hold open file handles to hardlinks''' return True +def nlinks(name): + '''return number of hardlinks for the given file''' + return os.lstat(name).st_nlink + def rcfiles(path): rcs = [os.path.join(path, 'hgrc')] rcdir = os.path.join(path, 'hgrc.d') diff --git a/mercurial/repo.py b/mercurial/repo.py --- a/mercurial/repo.py +++ b/mercurial/repo.py @@ -35,3 +35,6 @@ class repository(object): def cancopy(self): return self.local() + + def close(self): + pass diff --git a/mercurial/templatekw.py b/mercurial/templatekw.py --- a/mercurial/templatekw.py +++ b/mercurial/templatekw.py @@ -153,6 +153,10 @@ def showbranches(**args): if branch != 'default': return showlist('branch', [branch], plural='branches', **args) +def showbookmarks(**args): + bookmarks = args['ctx'].bookmarks() + return showlist('bookmark', bookmarks, **args) + def showchildren(**args): ctx = args['ctx'] childrevs = ['%d:%s' % (cctx, cctx) for cctx in ctx.children()] @@ -252,6 +256,7 @@ keywords = { 'author': showauthor, 'branch': showbranch, 'branches': showbranches, + 'bookmarks': showbookmarks, 'children': showchildren, 'date': showdate, 'desc': showdescription, diff --git a/mercurial/templates/map-cmdline.default b/mercurial/templates/map-cmdline.default --- a/mercurial/templates/map-cmdline.default +++ b/mercurial/templates/map-cmdline.default @@ -1,7 +1,7 @@ -changeset = 'changeset: {rev}:{node|short}\n{branches}{tags}{parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n' +changeset = 'changeset: {rev}:{node|short}\n{branches}{bookmarks}{tags}{parents}user: {author}\ndate: {date|date}\nsummary: {desc|firstline}\n\n' changeset_quiet = '{rev}:{node|short}\n' -changeset_verbose = 'changeset: {rev}:{node|short}\n{branches}{tags}{parents}user: {author}\ndate: {date|date}\n{files}{file_copies_switch}description:\n{desc|strip}\n\n\n' -changeset_debug = 'changeset: {rev}:{node}\n{branches}{tags}{parents}{manifest}user: {author}\ndate: {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies_switch}{extras}description:\n{desc|strip}\n\n\n' +changeset_verbose = 'changeset: {rev}:{node|short}\n{branches}{bookmarks}{tags}{parents}user: {author}\ndate: {date|date}\n{files}{file_copies_switch}description:\n{desc|strip}\n\n\n' +changeset_debug = 'changeset: {rev}:{node}\n{branches}{bookmarks}{tags}{parents}{manifest}user: {author}\ndate: {date|date}\n{file_mods}{file_adds}{file_dels}{file_copies_switch}{extras}description:\n{desc|strip}\n\n\n' start_files = 'files: ' file = ' {file}' end_files = '\n' @@ -21,4 +21,5 @@ parent = 'parent: {rev}:{node|forma manifest = 'manifest: {rev}:{node}\n' branch = 'branch: {branch}\n' tag = 'tag: {tag}\n' +bookmark = 'bookmark: {bookmark}\n' extra = 'extra: {key}={value|stringescape}\n' diff --git a/mercurial/templates/map-cmdline.xml b/mercurial/templates/map-cmdline.xml --- a/mercurial/templates/map-cmdline.xml +++ b/mercurial/templates/map-cmdline.xml @@ -1,9 +1,9 @@ header = '\n\n' footer = '\n' -changeset = '\n{branches}{tags}{parents}{author|person|xmlescape}\n{date|rfc3339date}\n{desc|xmlescape}\n\n' -changeset_verbose = '\n{branches}{tags}{parents}{author|person|xmlescape}\n{date|rfc3339date}\n{desc|xmlescape}\n\n{file_adds}{file_dels}{file_mods}\n{file_copies}\n' -changeset_debug = '\n{branches}{tags}{parents}{author|person|xmlescape}\n{date|rfc3339date}\n{desc|xmlescape}\n\n{file_adds}{file_dels}{file_mods}\n{file_copies}{extras}\n' +changeset = '\n{branches}{bookmarks}{tags}{parents}{author|person|xmlescape}\n{date|rfc3339date}\n{desc|xmlescape}\n\n' +changeset_verbose = '\n{branches}{bookmarks}{tags}{parents}{author|person|xmlescape}\n{date|rfc3339date}\n{desc|xmlescape}\n\n{file_adds}{file_dels}{file_mods}\n{file_copies}\n' +changeset_debug = '\n{branches}{bookmarks}{tags}{parents}{author|person|xmlescape}\n{date|rfc3339date}\n{desc|xmlescape}\n\n{file_adds}{file_dels}{file_mods}\n{file_copies}{extras}\n' file_add = '{file_add|xmlescape}\n' file_mod = '{file_mod|xmlescape}\n' @@ -16,4 +16,5 @@ end_file_copies = '\n' parent = '\n' branch = '{branch|xmlescape}\n' tag = '{tag|xmlescape}\n' +bookmark = '{bookmark|xmlescape}\n' extra = '{value|xmlescape}\n' diff --git a/mercurial/util.py b/mercurial/util.py --- a/mercurial/util.py +++ b/mercurial/util.py @@ -554,16 +554,6 @@ class path_auditor(object): # want to add "foo/bar/baz" before checking if there's a "foo/.hg" self.auditeddir.update(prefixes) -def nlinks(pathname): - """Return number of hardlinks for the given file.""" - return os.lstat(pathname).st_nlink - -if hasattr(os, 'link'): - os_link = os.link -else: - def os_link(src, dst): - raise OSError(0, _("Hardlinks not supported")) - def lookup_reg(key, name=None, scope=None): return None diff --git a/mercurial/win32.py b/mercurial/win32.py --- a/mercurial/win32.py +++ b/mercurial/win32.py @@ -5,73 +5,173 @@ # This software may be used and distributed according to the terms of the # GNU General Public License version 2 or any later version. -"""Utility functions that use win32 API. +import encoding +import ctypes, errno, os, struct, subprocess + +_kernel32 = ctypes.windll.kernel32 + +_BOOL = ctypes.c_long +_WORD = ctypes.c_ushort +_DWORD = ctypes.c_ulong +_LPCSTR = _LPSTR = ctypes.c_char_p +_HANDLE = ctypes.c_void_p +_HWND = _HANDLE + +_INVALID_HANDLE_VALUE = -1 + +# GetLastError +_ERROR_SUCCESS = 0 +_ERROR_INVALID_PARAMETER = 87 +_ERROR_INSUFFICIENT_BUFFER = 122 + +# WPARAM is defined as UINT_PTR (unsigned type) +# LPARAM is defined as LONG_PTR (signed type) +if ctypes.sizeof(ctypes.c_long) == ctypes.sizeof(ctypes.c_void_p): + _WPARAM = ctypes.c_ulong + _LPARAM = ctypes.c_long +elif ctypes.sizeof(ctypes.c_longlong) == ctypes.sizeof(ctypes.c_void_p): + _WPARAM = ctypes.c_ulonglong + _LPARAM = ctypes.c_longlong + +class _FILETIME(ctypes.Structure): + _fields_ = [('dwLowDateTime', _DWORD), + ('dwHighDateTime', _DWORD)] + +class _BY_HANDLE_FILE_INFORMATION(ctypes.Structure): + _fields_ = [('dwFileAttributes', _DWORD), + ('ftCreationTime', _FILETIME), + ('ftLastAccessTime', _FILETIME), + ('ftLastWriteTime', _FILETIME), + ('dwVolumeSerialNumber', _DWORD), + ('nFileSizeHigh', _DWORD), + ('nFileSizeLow', _DWORD), + ('nNumberOfLinks', _DWORD), + ('nFileIndexHigh', _DWORD), + ('nFileIndexLow', _DWORD)] + +# CreateFile +_FILE_SHARE_READ = 0x00000001 +_FILE_SHARE_WRITE = 0x00000002 +_FILE_SHARE_DELETE = 0x00000004 + +_OPEN_EXISTING = 3 + +# Process Security and Access Rights +_PROCESS_QUERY_INFORMATION = 0x0400 + +# GetExitCodeProcess +_STILL_ACTIVE = 259 + +# registry +_HKEY_CURRENT_USER = 0x80000001L +_HKEY_LOCAL_MACHINE = 0x80000002L +_KEY_READ = 0x20019 +_REG_SZ = 1 +_REG_DWORD = 4 -Mark Hammond's win32all package allows better functionality on -Windows. This module overrides definitions in util.py. If not -available, import of this module will fail, and generic code will be -used. -""" +class _STARTUPINFO(ctypes.Structure): + _fields_ = [('cb', _DWORD), + ('lpReserved', _LPSTR), + ('lpDesktop', _LPSTR), + ('lpTitle', _LPSTR), + ('dwX', _DWORD), + ('dwY', _DWORD), + ('dwXSize', _DWORD), + ('dwYSize', _DWORD), + ('dwXCountChars', _DWORD), + ('dwYCountChars', _DWORD), + ('dwFillAttribute', _DWORD), + ('dwFlags', _DWORD), + ('wShowWindow', _WORD), + ('cbReserved2', _WORD), + ('lpReserved2', ctypes.c_char_p), + ('hStdInput', _HANDLE), + ('hStdOutput', _HANDLE), + ('hStdError', _HANDLE)] + +class _PROCESS_INFORMATION(ctypes.Structure): + _fields_ = [('hProcess', _HANDLE), + ('hThread', _HANDLE), + ('dwProcessId', _DWORD), + ('dwThreadId', _DWORD)] + +_DETACHED_PROCESS = 0x00000008 +_STARTF_USESHOWWINDOW = 0x00000001 +_SW_HIDE = 0 -import win32api +class _COORD(ctypes.Structure): + _fields_ = [('X', ctypes.c_short), + ('Y', ctypes.c_short)] + +class _SMALL_RECT(ctypes.Structure): + _fields_ = [('Left', ctypes.c_short), + ('Top', ctypes.c_short), + ('Right', ctypes.c_short), + ('Bottom', ctypes.c_short)] + +class _CONSOLE_SCREEN_BUFFER_INFO(ctypes.Structure): + _fields_ = [('dwSize', _COORD), + ('dwCursorPosition', _COORD), + ('wAttributes', _WORD), + ('srWindow', _SMALL_RECT), + ('dwMaximumWindowSize', _COORD)] -import errno, os, sys, pywintypes, win32con, win32file, win32process -import winerror, win32gui, win32console -import osutil, encoding -from win32com.shell import shell, shellcon +_STD_ERROR_HANDLE = 0xfffffff4L # (DWORD)-12 + +def _raiseoserror(name): + err = ctypes.WinError() + raise OSError(err.errno, '%s: %s' % (name, err.strerror)) + +def _getfileinfo(name): + fh = _kernel32.CreateFileA(name, 0, + _FILE_SHARE_READ | _FILE_SHARE_WRITE | _FILE_SHARE_DELETE, + None, _OPEN_EXISTING, 0, None) + if fh == _INVALID_HANDLE_VALUE: + _raiseoserror(name) + try: + fi = _BY_HANDLE_FILE_INFORMATION() + if not _kernel32.GetFileInformationByHandle(fh, ctypes.byref(fi)): + _raiseoserror(name) + return fi + finally: + _kernel32.CloseHandle(fh) def os_link(src, dst): - try: - win32file.CreateHardLink(dst, src) - except pywintypes.error: - raise OSError(errno.EINVAL, 'target implements hardlinks improperly') - except NotImplementedError: # Another fake error win Win98 - raise OSError(errno.EINVAL, 'Hardlinking not supported') + if not _kernel32.CreateHardLinkA(dst, src, None): + _raiseoserror(src) -def _getfileinfo(pathname): - """Return number of hardlinks for the given file.""" - try: - fh = win32file.CreateFile(pathname, - win32file.GENERIC_READ, win32file.FILE_SHARE_READ, - None, win32file.OPEN_EXISTING, 0, None) - except pywintypes.error: - raise OSError(errno.ENOENT, 'The system cannot find the file specified') - try: - return win32file.GetFileInformationByHandle(fh) - finally: - fh.Close() - -def nlinks(pathname): - """Return number of hardlinks for the given file.""" - return _getfileinfo(pathname)[7] +def nlinks(name): + '''return number of hardlinks for the given file''' + return _getfileinfo(name).nNumberOfLinks def samefile(fpath1, fpath2): - """Returns whether fpath1 and fpath2 refer to the same file. This is only - guaranteed to work for files, not directories.""" + '''Returns whether fpath1 and fpath2 refer to the same file. This is only + guaranteed to work for files, not directories.''' res1 = _getfileinfo(fpath1) res2 = _getfileinfo(fpath2) - # Index 4 is the volume serial number, and 8 and 9 contain the file ID - return res1[4] == res2[4] and res1[8] == res2[8] and res1[9] == res2[9] + return (res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber + and res1.nFileIndexHigh == res2.nFileIndexHigh + and res1.nFileIndexLow == res2.nFileIndexLow) def samedevice(fpath1, fpath2): - """Returns whether fpath1 and fpath2 are on the same device. This is only - guaranteed to work for files, not directories.""" + '''Returns whether fpath1 and fpath2 are on the same device. This is only + guaranteed to work for files, not directories.''' res1 = _getfileinfo(fpath1) res2 = _getfileinfo(fpath2) - return res1[4] == res2[4] + return res1.dwVolumeSerialNumber == res2.dwVolumeSerialNumber def testpid(pid): '''return True if pid is still running or unable to determine, False otherwise''' - try: - handle = win32api.OpenProcess( - win32con.PROCESS_QUERY_INFORMATION, False, pid) - if handle: - status = win32process.GetExitCodeProcess(handle) - return status == win32con.STILL_ACTIVE - except pywintypes.error, details: - return details[0] != winerror.ERROR_INVALID_PARAMETER - return True + h = _kernel32.OpenProcess(_PROCESS_QUERY_INFORMATION, False, pid) + if h: + try: + status = _DWORD() + if _kernel32.GetExitCodeProcess(h, ctypes.byref(status)): + return status.value == _STILL_ACTIVE + finally: + _kernel32.CloseHandle(h) + return _kernel32.GetLastError() != _ERROR_INVALID_PARAMETER def lookup_reg(key, valname=None, scope=None): ''' Look up a key/value name in the Windows registry. @@ -82,101 +182,137 @@ def lookup_reg(key, valname=None, scope= a sequence of scopes to look up in order. Default (CURRENT_USER, LOCAL_MACHINE). ''' - try: - from _winreg import HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE, \ - QueryValueEx, OpenKey - except ImportError: - return None - + adv = ctypes.windll.advapi32 + byref = ctypes.byref if scope is None: - scope = (HKEY_CURRENT_USER, HKEY_LOCAL_MACHINE) + scope = (_HKEY_CURRENT_USER, _HKEY_LOCAL_MACHINE) elif not isinstance(scope, (list, tuple)): scope = (scope,) for s in scope: + kh = _HANDLE() + res = adv.RegOpenKeyExA(s, key, 0, _KEY_READ, ctypes.byref(kh)) + if res != _ERROR_SUCCESS: + continue try: - val = QueryValueEx(OpenKey(s, key), valname)[0] - # never let a Unicode string escape into the wild - return encoding.tolocal(val.encode('UTF-8')) - except EnvironmentError: - pass + size = _DWORD(600) + type = _DWORD() + buf = ctypes.create_string_buffer(size.value + 1) + res = adv.RegQueryValueExA(kh.value, valname, None, + byref(type), buf, byref(size)) + if res != _ERROR_SUCCESS: + continue + if type.value == _REG_SZ: + # never let a Unicode string escape into the wild + return encoding.tolocal(buf.value.encode('UTF-8')) + elif type.value == _REG_DWORD: + fmt = ' b @@ -66,8 +66,8 @@ bookmarks revset $ hg log -r 'bookmark()' changeset: 1:925d80f479bb - tag: X - tag: X2 + bookmark: X + bookmark: X2 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -76,8 +76,8 @@ bookmarks revset $ hg log -r 'bookmark(Y)' $ hg log -r 'bookmark(X2)' changeset: 1:925d80f479bb - tag: X - tag: X2 + bookmark: X + bookmark: X2 tag: tip user: test date: Thu Jan 01 00:00:00 1970 +0000 @@ -89,8 +89,8 @@ bookmarks revset bookmarks X and X2 moved to rev 1, Y at rev -1 $ hg bookmarks + * X 1:925d80f479bb * X2 1:925d80f479bb - * X 1:925d80f479bb Y -1:000000000000 bookmark rev 0 again @@ -104,10 +104,10 @@ bookmark rev 0 again bookmarks X and X2 moved to rev 2, Y at rev -1, Z at rev 0 $ hg bookmarks + * X 2:0316ce92851d * X2 2:0316ce92851d - * X 2:0316ce92851d + Y -1:000000000000 Z 0:f7b1eb17ad24 - Y -1:000000000000 rename nonexistent bookmark @@ -166,10 +166,10 @@ look up stripped bookmark name $ hg log -r '"x y"' changeset: 2:0316ce92851d - tag: X2 - tag: Y + bookmark: X2 + bookmark: Y + bookmark: x y tag: tip - tag: x y user: test date: Thu Jan 01 00:00:00 1970 +0000 summary: 2 diff --git a/tests/test-bundle.t b/tests/test-bundle.t --- a/tests/test-bundle.t +++ b/tests/test-bundle.t @@ -188,6 +188,13 @@ Log -R full.hg in fresh empty date: Thu Jan 01 00:00:00 1970 +0000 summary: 0.0 +Make sure bundlerepo doesn't leak tempfiles (issue2491) + + $ ls .hg + 00changelog.i + cache + requires + store Pull ../full.hg into empty (with hook)