From: Stefan Huber Date: Wed, 7 Nov 2012 20:30:36 +0000 (+0100) Subject: Add hg config X-Git-Url: https://git.sthu.org/?a=commitdiff_plain;h=90c0e76b6b0f0eb20a9a9e97c0ca6ba5fcd7b5d1;p=shutils.git Add hg config --- diff --git a/dotfiles/hg/hgext/hgshelve.py b/dotfiles/hg/hgext/hgshelve.py new file mode 100644 index 0000000..a8069da --- /dev/null +++ b/dotfiles/hg/hgext/hgshelve.py @@ -0,0 +1,645 @@ +# shelve.py +# +# Copyright 2007 Bryan O'Sullivan +# Copyright 2007 TK Soh +# +# This software may be used and distributed according to the terms of +# the GNU General Public License, incorporated herein by reference. + +'''interactive change selection to set aside that may be restored later''' + +from mercurial.i18n import _ +from mercurial import cmdutil, commands, cmdutil, hg, mdiff, patch, revlog +from mercurial import util, fancyopts, extensions +import copy, cStringIO, errno, operator, os, re, shutil, tempfile + +lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)') + +def scanpatch(fp): + lr = patch.linereader(fp) + + def scanwhile(first, p): + lines = [first] + while True: + line = lr.readline() + if not line: + break + if p(line): + lines.append(line) + else: + lr.push(line) + break + return lines + + while True: + line = lr.readline() + if not line: + break + if line.startswith('diff --git a/'): + def notheader(line): + s = line.split(None, 1) + return not s or s[0] not in ('---', 'diff') + header = scanwhile(line, notheader) + fromfile = lr.readline() + if fromfile.startswith('---'): + tofile = lr.readline() + header += [fromfile, tofile] + else: + lr.push(fromfile) + yield 'file', header + elif line[0] == ' ': + yield 'context', scanwhile(line, lambda l: l[0] in ' \\') + elif line[0] in '-+': + yield 'hunk', scanwhile(line, lambda l: l[0] in '-+\\') + else: + m = lines_re.match(line) + if m: + yield 'range', m.groups() + else: + raise patch.PatchError('unknown patch content: %r' % line) + +class header(object): + diff_re = re.compile('diff --git a/(.*) b/(.*)$') + allhunks_re = re.compile('(?:index|new file|deleted file) ') + pretty_re = re.compile('(?:new file|deleted file) ') + special_re = re.compile('(?:index|new|deleted|copy|rename) ') + + def __init__(self, header): + self.header = header + self.hunks = [] + + def binary(self): + for h in self.header: + if h.startswith('index '): + return True + + def pretty(self, fp): + for h in self.header: + if h.startswith('index '): + fp.write(_('this modifies a binary file (all or nothing)\n')) + break + if self.pretty_re.match(h): + fp.write(h) + if self.binary(): + fp.write(_('this is a binary file\n')) + break + if h.startswith('---'): + fp.write(_('%d hunks, %d lines changed\n') % + (len(self.hunks), + sum([h.added + h.removed for h in self.hunks]))) + break + fp.write(h) + + def write(self, fp): + fp.write(''.join(self.header)) + + def allhunks(self): + for h in self.header: + if self.allhunks_re.match(h): + return True + + def files(self): + fromfile, tofile = self.diff_re.match(self.header[0]).groups() + if fromfile == tofile: + return [fromfile] + return [fromfile, tofile] + + def filename(self): + return self.files()[-1] + + def __repr__(self): + return '
' % (' '.join(map(repr, self.files()))) + + def special(self): + for h in self.header: + if self.special_re.match(h): + return True + +def countchanges(hunk): + add = len([h for h in hunk if h[0] == '+']) + rem = len([h for h in hunk if h[0] == '-']) + return add, rem + +class hunk(object): + maxcontext = 3 + + def __init__(self, header, fromline, toline, proc, before, hunk, after): + def trimcontext(number, lines): + delta = len(lines) - self.maxcontext + if False and delta > 0: + return number + delta, lines[:self.maxcontext] + return number, lines + + self.header = header + self.fromline, self.before = trimcontext(fromline, before) + self.toline, self.after = trimcontext(toline, after) + self.proc = proc + self.hunk = hunk + self.added, self.removed = countchanges(self.hunk) + + def __cmp__(self, rhs): + # since the hunk().toline needs to be adjusted when hunks are + # removed/added, we can't take it into account when we cmp + attrs = ['header', 'fromline', 'proc', 'hunk', 'added', 'removed'] + for attr in attrs: + selfattr = getattr(self, attr, None) + rhsattr = getattr(rhs, attr, None) + + if selfattr is None or rhsattr is None: + raise util.Abort(_('non-existant attribute %s') % attr) + + rv = cmp(selfattr, rhsattr) + if rv != 0: + return rv + return rv + + + def write(self, fp): + delta = len(self.before) + len(self.after) + if self.after and self.after[-1] == '\\ No newline at end of file\n': + delta -= 1 + fromlen = delta + self.removed + tolen = delta + self.added + fp.write('@@ -%d,%d +%d,%d @@%s\n' % + (self.fromline, fromlen, self.toline, tolen, + self.proc and (' ' + self.proc))) + fp.write(''.join(self.before + self.hunk + self.after)) + + pretty = write + + def filename(self): + return self.header.filename() + + def __repr__(self): + return '' % (self.filename(), self.fromline) + +def parsepatch(fp): + class parser(object): + def __init__(self): + self.fromline = 0 + self.toline = 0 + self.proc = '' + self.header = None + self.context = [] + self.before = [] + self.hunk = [] + self.stream = [] + + def addrange(self, (fromstart, fromend, tostart, toend, proc)): + self.fromline = int(fromstart) + self.toline = int(tostart) + self.proc = proc + + def addcontext(self, context): + if self.hunk: + h = hunk(self.header, self.fromline, self.toline, self.proc, + self.before, self.hunk, context) + self.header.hunks.append(h) + self.stream.append(h) + self.fromline += len(self.before) + h.removed + self.toline += len(self.before) + h.added + self.before = [] + self.hunk = [] + self.proc = '' + self.context = context + + def addhunk(self, hunk): + if self.context: + self.before = self.context + self.context = [] + self.hunk = hunk + + def newfile(self, hdr): + self.addcontext([]) + h = header(hdr) + self.stream.append(h) + self.header = h + + def finished(self): + self.addcontext([]) + return self.stream + + transitions = { + 'file': {'context': addcontext, + 'file': newfile, + 'hunk': addhunk, + 'range': addrange}, + 'context': {'file': newfile, + 'hunk': addhunk, + 'range': addrange}, + 'hunk': {'context': addcontext, + 'file': newfile, + 'range': addrange}, + 'range': {'context': addcontext, + 'hunk': addhunk}, + } + + p = parser() + + state = 'context' + for newstate, data in scanpatch(fp): + try: + p.transitions[state][newstate](p, data) + except KeyError: + raise patch.PatchError('unhandled transition: %s -> %s' % + (state, newstate)) + state = newstate + return p.finished() + +def filterpatch(ui, chunks, shouldprompt=True): + chunks = list(chunks) + chunks.reverse() + seen = {} + def consumefile(): + consumed = [] + while chunks: + if isinstance(chunks[-1], header): + break + else: + consumed.append(chunks.pop()) + return consumed + + resp_all = [None] + + """ If we're not to prompt (i.e. they specified the --all flag) + we pre-emptively set the 'all' flag """ + if shouldprompt == False: + resp_all = ['y'] + + resp_file = [None] + applied = {} + def prompt(query): + if resp_all[0] is not None: + return resp_all[0] + if resp_file[0] is not None: + return resp_file[0] + while True: + resps = _('[Ynsfdaq?]') + choices = (_('&Yes, shelve this change'), + _('&No, skip this change'), + _('&Skip remaining changes to this file'), + _('Shelve remaining changes to this &file'), + _('&Done, skip remaining changes and files'), + _('Shelve &all changes to all remaining files'), + _('&Quit, shelving no changes'), + _('&?')) + r = ui.promptchoice("%s %s " % (query, resps), choices) + if r == 7: + c = shelve.__doc__.find('y - shelve this change') + for l in shelve.__doc__[c:].splitlines(): + if l: ui.write(_(l.strip()) + '\n') + continue + elif r == 0: # yes + ret = 'y' + elif r == 1: # no + ret = 'n' + elif r == 2: # Skip + ret = resp_file[0] = 'n' + elif r == 3: # file (shelve remaining) + ret = resp_file[0] = 'y' + elif r == 4: # done, skip remaining + ret = resp_all[0] = 'n' + elif r == 5: # all + ret = resp_all[0] = 'y' + elif r == 6: # quit + raise util.Abort(_('user quit')) + return ret + while chunks: + chunk = chunks.pop() + if isinstance(chunk, header): + resp_file = [None] + fixoffset = 0 + hdr = ''.join(chunk.header) + if hdr in seen: + consumefile() + continue + seen[hdr] = True + if resp_all[0] is None: + chunk.pretty(ui) + if shouldprompt == True: + r = prompt(_('shelve changes to %s?') % + _(' and ').join(map(repr, chunk.files()))) + else: + r = 'y' + + if r == 'y': + applied[chunk.filename()] = [chunk] + if chunk.allhunks(): + applied[chunk.filename()] += consumefile() + else: + consumefile() + else: + if resp_file[0] is None and resp_all[0] is None: + chunk.pretty(ui) + r = prompt(_('shelve this change to %r?') % + chunk.filename()) + if r == 'y': + if fixoffset: + chunk = copy.copy(chunk) + chunk.toline += fixoffset + applied[chunk.filename()].append(chunk) + else: + fixoffset += chunk.removed - chunk.added + return reduce(operator.add, [h for h in applied.itervalues() + if h[0].special() or len(h) > 1], []) + +def refilterpatch(allchunk, selected): + ''' return unshelved chunks of files to be shelved ''' + l = [] + fil = [] + for c in allchunk: + if isinstance(c, header): + if len(l) > 1 and l[0] in selected: + fil += l + l = [c] + elif c not in selected: + l.append(c) + if len(l) > 1 and l[0] in selected: + fil += l + return fil + +def makebackup(ui, repo, dir, files): + try: + os.mkdir(dir) + except OSError, err: + if err.errno != errno.EEXIST: + raise + + backups = {} + for f in files: + fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.', + dir=dir) + os.close(fd) + ui.debug('backup %r as %r\n' % (f, tmpname)) + util.copyfile(repo.wjoin(f), tmpname) + backups[f] = tmpname + + return backups + +def getshelfpath(repo, name): + if name: + shelfpath = "shelves/" + name + else: + # Check if a shelf from an older version exists + if os.path.isfile(repo.join('shelve')): + shelfpath = 'shelve' + else: + shelfpath = "shelves/default" + + return shelfpath + +def shelve(ui, repo, *pats, **opts): + '''interactively select changes to set aside + + If a list of files is omitted, all changes reported by "hg status" + will be candidates for shelving. + + You will be prompted for whether to shelve changes to each + modified file, and for files with multiple changes, for each + change to use. + + The shelve command works with the Color extension to display + diffs in color. + + On each prompt, the following responses are possible:: + + y - shelve this change + n - skip this change + + s - skip remaining changes to this file + f - shelve remaining changes to this file + + d - done, skip remaining changes and files + a - shelve all changes to all remaining files + q - quit, shelving no changes + + ? - display help''' + + if not ui.interactive: + raise util.Abort(_('shelve can only be run interactively')) + + # List all the active shelves by name and return ' + if opts['list']: + listshelves(ui,repo) + return + + forced = opts['force'] or opts['append'] + + # Shelf name and path + shelfname = opts.get('name') + shelfpath = getshelfpath(repo, shelfname) + + if os.path.exists(repo.join(shelfpath)) and not forced: + raise util.Abort(_('shelve data already exists')) + + def shelvefunc(ui, repo, message, match, opts): + changes = repo.status(match=match)[:5] + modified, added, removed = changes[:3] + files = modified + added + removed + diffopts = mdiff.diffopts(git=True, nodates=True) + patch_diff = ''.join(patch.diff(repo, repo.dirstate.parents()[0], + match=match, changes=changes, opts=diffopts)) + + fp = cStringIO.StringIO(patch_diff) + ac = parsepatch(fp) + fp.close() + + chunks = filterpatch(ui, ac, not opts['all']) + rc = refilterpatch(ac, chunks) + + contenders = {} + for h in chunks: + try: contenders.update(dict.fromkeys(h.files())) + except AttributeError: pass + + newfiles = [f for f in files if f in contenders] + + if not newfiles: + ui.status(_('no changes to shelve\n')) + return 0 + + modified = dict.fromkeys(changes[0]) + + backupdir = repo.join('shelve-backups') + + try: + bkfiles = [f for f in newfiles if f in modified] + backups = makebackup(ui, repo, backupdir, bkfiles) + + # patch to shelve + sp = cStringIO.StringIO() + for c in chunks: + if c.filename() in backups: + c.write(sp) + doshelve = sp.tell() + sp.seek(0) + + # patch to apply to shelved files + fp = cStringIO.StringIO() + for c in rc: + if c.filename() in backups: + c.write(fp) + dopatch = fp.tell() + fp.seek(0) + + try: + # 3a. apply filtered patch to clean repo (clean) + if backups: + hg.revert(repo, repo.dirstate.parents()[0], backups.has_key) + + # 3b. apply filtered patch to clean repo (apply) + if dopatch: + ui.debug('applying patch\n') + ui.debug(fp.getvalue()) + patch.internalpatch(ui, repo, fp, 1) + del fp + + # 3c. apply filtered patch to clean repo (shelve) + if doshelve: + ui.debug("saving patch to shelve\n") + if opts['append']: + f = repo.opener(shelfpath, "a") + else: + f = repo.opener(shelfpath, "w") + f.write(sp.getvalue()) + del f + del sp + except: + try: + for realname, tmpname in backups.iteritems(): + ui.debug('restoring %r to %r\n' % (tmpname, realname)) + util.copyfile(tmpname, repo.wjoin(realname)) + ui.debug('removing shelve file\n') + os.unlink(repo.join(shelfpath)) + except OSError: + pass + + return 0 + finally: + try: + for realname, tmpname in backups.iteritems(): + ui.debug('removing backup for %r : %r\n' % (realname, tmpname)) + os.unlink(tmpname) + os.rmdir(backupdir) + except OSError: + pass + fancyopts.fancyopts([], commands.commitopts, opts) + + # wrap ui.write so diff output can be labeled/colorized + def wrapwrite(orig, *args, **kw): + label = kw.pop('label', '') + if label: label += ' ' + for chunk, l in patch.difflabel(lambda: args): + orig(chunk, label=label + l) + oldwrite = ui.write + extensions.wrapfunction(ui, 'write', wrapwrite) + try: + return cmdutil.commit(ui, repo, shelvefunc, pats, opts) + finally: + ui.write = oldwrite + +def listshelves(ui, repo): + # Check for shelve file at old location first + if os.path.isfile(repo.join('shelve')): + ui.status('default\n') + + # Now go through all the files in the shelves folder and list them out + dirname = repo.join('shelves') + if os.path.isdir(dirname): + for filename in os.listdir(repo.join('shelves')): + ui.status(filename + '\n') + +def unshelve(ui, repo, **opts): + '''restore shelved changes''' + + # Shelf name and path + shelfname = opts.get('name') + shelfpath = getshelfpath(repo, shelfname) + + # List all the active shelves by name and return ' + if opts['list']: + listshelves(ui,repo) + return + + try: + patch_diff = repo.opener(shelfpath).read() + fp = cStringIO.StringIO(patch_diff) + if opts['inspect']: + # wrap ui.write so diff output can be labeled/colorized + def wrapwrite(orig, *args, **kw): + label = kw.pop('label', '') + if label: label += ' ' + for chunk, l in patch.difflabel(lambda: args): + orig(chunk, label=label + l) + oldwrite = ui.write + extensions.wrapfunction(ui, 'write', wrapwrite) + try: + ui.status(fp.getvalue()) + finally: + ui.write = oldwrite + else: + files = [] + ac = parsepatch(fp) + for chunk in ac: + if isinstance(chunk, header): + files += chunk.files() + backupdir = repo.join('shelve-backups') + backups = makebackup(ui, repo, backupdir, set(files)) + + ui.debug('applying shelved patch\n') + patchdone = 0 + try: + try: + fp.seek(0) + patch.internalpatch(ui, repo, fp, 1) + patchdone = 1 + except: + if opts['force']: + patchdone = 1 + else: + ui.status('restoring backup files\n') + for realname, tmpname in backups.iteritems(): + ui.debug('restoring %r to %r\n' % + (tmpname, realname)) + util.copyfile(tmpname, repo.wjoin(realname)) + finally: + try: + ui.debug('removing backup files\n') + shutil.rmtree(backupdir, True) + except OSError: + pass + + if patchdone: + ui.debug("removing shelved patches\n") + os.unlink(repo.join(shelfpath)) + ui.status("unshelve completed\n") + except IOError: + ui.warn('nothing to unshelve\n') + +cmdtable = { + "shelve": + (shelve, + [('A', 'addremove', None, + _('mark new/missing files as added/removed before shelving')), + ('f', 'force', None, + _('overwrite existing shelve data')), + ('a', 'append', None, + _('append to existing shelve data')), + ('', 'all', None, + _('shelve all changes')), + ('n', 'name', '', + _('shelve changes to specified shelf name')), + ('l', 'list', None, _('list active shelves')), + ] + commands.walkopts, + _('hg shelve [OPTION]... [FILE]...')), + "unshelve": + (unshelve, + [('i', 'inspect', None, _('inspect shelved changes only')), + ('f', 'force', None, + _('proceed even if patches do not unshelve cleanly')), + ('n', 'name', '', + _('unshelve changes from specified shelf name')), + ('l', 'list', None, _('list active shelves')), + ], + _('hg unshelve [OPTION]...')), +} diff --git a/dotfiles/hg/hgrc b/dotfiles/hg/hgrc new file mode 100644 index 0000000..d473cbd --- /dev/null +++ b/dotfiles/hg/hgrc @@ -0,0 +1,43 @@ +[ui] +username = First Last +merge = gvimdiff +editor = vim + +[merge-tools] +gvimdiff.executable = gvim +gvimdiff.args = -f -d $base $local $output $other -c "wincmd J" + +[web] +cacerts = + +[auth] + +[hostfingerprints] + +[extensions] +color = +pager = +transplant = +rebase = +hgext.mq = +hgext.patchbomb = +graphlog = +hgext.extdiff = +fetch = +hgext.hgshelve = ~/.hgext/hgshelve.py + +[pager] +pager = LESS='FSRX' less +#quiet = True +#attend = diff,status,log,glog + +[diff] +git = True + +[extdiff] +# if cwdiff is in your path, otherwise set to wherever you've installed cwdiff +cmd.wdiff = cwdiff +#add --no-color below, if you dislike colors! +opts.wdiff = --diff --ab + +