by
Changes to 6 files · Browse files at 0a94fcb73a42 Showing diff from parent 3f90b5df7167 095d7aedcc48 Diff from another changeset...
|
|
@@ -6,28 +6,19 @@ #
import os
-import threading
-import StringIO
-import sys
-import shutil
-import tempfile
-import datetime
-import cPickle
-
import pygtk
pygtk.require('2.0')
+import errno
import gtk
-import gobject
import pango
+import tempfile
+import cStringIO
from mercurial.i18n import _
-from mercurial.node import *
-from mercurial import cmdutil, util, ui, hg, commands, patch
-from hgext import extdiff
+from mercurial import ui, hg
from shlib import shell_notify
from gdialog import *
-from gtools import cmdtable
-from status import GStatus
+from status import GStatus, DM_REJECTED, DM_HEADER_CHUNK, DM_CHUNK_ID
from hgcmd import CmdDialog
from hglib import fromutf
@@ -96,9 +87,10 @@ tbbuttons.insert(2, gtk.SeparatorToolItem())
self._undo_button = self.make_toolbutton(gtk.STOCK_UNDO, '_Undo',
self._undo_clicked, tip='undo recent commit')
+ self._commit_button = self.make_toolbutton(gtk.STOCK_OK, '_Commit',
+ self._commit_clicked, tip='commit')
tbbuttons.insert(2, self._undo_button)
- tbbuttons.insert(2, self.make_toolbutton(gtk.STOCK_OK, '_Commit',
- self._commit_clicked, tip='commit'))
+ tbbuttons.insert(2, self._commit_button)
return tbbuttons
@@ -167,6 +159,12 @@ self._vpaned.add1(vbox)
self._vpaned.add2(status_body)
self._vpaned.set_position(self._setting_vpos)
+
+ # make ctrl-o trigger commit button
+ accel_group = gtk.AccelGroup()
+ self.add_accel_group(accel_group)
+ self._commit_button.add_accelerator("clicked", accel_group, ord("o"),
+ gtk.gdk.CONTROL_MASK, gtk.ACCEL_VISIBLE)
return self._vpaned
@@ -190,7 +188,10 @@ # If there are more than a few character typed into the commit
# message, ask if the exit should continue.
live = False
- if self.text.get_buffer().get_char_count() > 10:
+ buffer = self.text.get_buffer()
+ begin, end = buffer.get_bounds()
+ cur_msg = buffer.get_text(begin, end)
+ if buffer.get_char_count() > 10 and cur_msg != self.qheader:
dialog = Confirm('Exit', [], self, 'Discard commit message and exit?')
if dialog.run() == gtk.RESPONSE_NO:
live = True
@@ -198,8 +199,10 @@
def reload_status(self):
+ if not self._ready: return False
success = GStatus.reload_status(self)
self._check_merge()
+ self._check_patch_queue()
self._check_undo()
return success
@@ -215,12 +218,11 @@ def _check_merge(self):
# disable the checkboxes on the filelist if repo in merging state
merged = len(self.repo.changectx(None).parents()) > 1
- cbcell = self.tree.get_column(0).get_cell_renderers()[0]
- cbcell.set_property("activatable", not merged)
self.get_toolbutton('Re_vert').set_sensitive(not merged)
self.get_toolbutton('_Add').set_sensitive(not merged)
self.get_toolbutton('_Remove').set_sensitive(not merged)
+ self.get_toolbutton('Move').set_sensitive(not merged)
if merged:
# select all changes if repo is merged
@@ -233,6 +235,19 @@ self.text.get_buffer().set_text('merge')
+ def _check_patch_queue(self):
+ '''See if an MQ patch is applied, switch to qrefresh mode'''
+ self.qheader = None
+ if not hasattr(self.repo, 'mq'): return
+ if not self.repo.mq.applied: return
+ patch = self.repo.mq.lookup('qtip')
+ ph = self.repo.mq.readheaders(patch)
+ title = os.path.basename(self.repo.root) + ' qrefresh ' + patch
+ self.set_title(title)
+ self.qheader = '\n'.join(ph.message)
+ self.text.get_buffer().set_text(self.qheader)
+ self.get_toolbutton('_Commit').set_label('QRefresh')
+
def _commit_clicked(self, toolbutton, data=None):
if not self._ready_message():
return True
@@ -241,6 +256,7 @@ # as of Mercurial 1.0, merges must be committed without
# specifying file list.
self._hg_commit([])
+ self.reload_status()
else:
commitable = 'MAR'
addremove_list = self._relevant_files('?!')
@@ -249,11 +265,92 @@
commit_list = self._relevant_files(commitable)
if len(commit_list) > 0:
- self._hg_commit(commit_list)
+ self._commit_selected(commit_list)
+ return True
else:
Prompt('Nothing Commited', 'No committable files selected', self).run()
return True
+ def _commit_selected(self, files):
+ import hgshelve
+ # 1a. get list of chunks not rejected
+ hlist = [x[DM_CHUNK_ID] for x in self.diff_model if not x[DM_REJECTED]]
+ repo, chunks, ui = self.repo, self._shelve_chunks, self.ui
+
+ # 2. backup changed files, so we can restore them in the end
+ backups = {}
+ backupdir = repo.join('record-backups')
+ try:
+ os.mkdir(backupdir)
+ except OSError, err:
+ if err.errno != errno.EEXIST:
+ Prompt('Commit', 'Unable to create ' + backupdir,
+ self).run()
+ return
+ try:
+ # backup continues
+ for f in files:
+ if f not in self._filechunks: continue
+ if len(self._filechunks[f]) == 1: continue
+ if f not in self.modified: continue
+ fd, tmpname = tempfile.mkstemp(prefix=f.replace('/', '_')+'.',
+ dir=backupdir)
+ os.close(fd)
+ ui.debug(_('backup %r as %r\n') % (f, tmpname))
+ util.copyfile(repo.wjoin(f), tmpname)
+ backups[f] = tmpname
+
+ fp = cStringIO.StringIO()
+ for n, c in enumerate(chunks):
+ if c.filename() in backups and n in hlist:
+ c.write(fp)
+ dopatch = fp.tell()
+ fp.seek(0)
+
+ # 3a. apply filtered patch to clean repo (clean)
+ if backups:
+ hg.revert(repo, repo.dirstate.parents()[0], backups.has_key)
+
+ # 3b. (apply)
+ if dopatch:
+ try:
+ ui.debug(_('applying patch\n'))
+ ui.debug(fp.getvalue())
+ patch.internalpatch(fp, ui, 1, repo.root)
+ except patch.PatchError, err:
+ s = str(err)
+ if s:
+ raise util.Abort(s)
+ else:
+ Prompt('Commit', 'Unable to apply patch', self).run()
+ raise util.Abort(_('patch failed to apply'))
+ del fp
+
+ # 4. We prepared working directory according to filtered patch.
+ # Now is the time to delegate the job to commit/qrefresh or the like!
+
+ # it is important to first chdir to repo root -- we'll call a
+ # highlevel command with list of pathnames relative to repo root
+ cwd = os.getcwd()
+ os.chdir(repo.root)
+ try:
+ self._hg_commit(files)
+ finally:
+ os.chdir(cwd)
+
+ return 0
+ finally:
+ # 5. finally restore backed-up files
+ try:
+ for realname, tmpname in backups.iteritems():
+ ui.debug(_('restoring %r to %r\n') % (tmpname, realname))
+ util.copyfile(tmpname, repo.wjoin(realname))
+ os.unlink(tmpname)
+ os.rmdir(backupdir)
+ except OSError:
+ pass
+ self.reload_status()
+
def _commit_file(self, stat, file):
if self._ready_message():
@@ -332,11 +429,15 @@ # call the threaded CmdDialog to do the commit, so the the large commit
# won't get locked up by potential large commit. CmdDialog will also
# display the progress of the commit operation.
- cmdline = ["hg", "commit", "--verbose", "--repository", self.repo.root]
- if self.opts['addremove']:
- cmdline += ['--addremove']
- cmdline += ['--message', fromutf(self.opts['message'])]
- cmdline += [self.repo.wjoin(x) for x in files]
+ if self.qheader:
+ cmdline = ["hg", "qrefresh", "--verbose", "--repository", self.repo.root]
+ cmdline += ['--message', fromutf(self.opts['message'])]
+ else:
+ cmdline = ["hg", "commit", "--verbose", "--repository", self.repo.root]
+ if self.opts['addremove']:
+ cmdline += ['--addremove']
+ cmdline += ['--message', fromutf(self.opts['message'])]
+ cmdline += [self.repo.wjoin(x) for x in files]
dialog = CmdDialog(cmdline, True)
dialog.set_transient_for(self)
dialog.run()
@@ -344,11 +445,11 @@
# refresh overlay icons and commit dialog
if dialog.return_code() == 0:
- self.text.set_buffer(gtk.TextBuffer())
- self._update_recent_messages(self.opts['message'])
shell_notify([self.cwd] + files)
- self._last_commit_id = self._get_tip_rev(True)
- self.reload_status()
+ if not self.qheader:
+ self.text.set_buffer(gtk.TextBuffer())
+ self._update_recent_messages(self.opts['message'])
+ self._last_commit_id = self._get_tip_rev(True)
def _get_tip_rev(self, refresh=False):
if refresh:
@@ -382,7 +483,7 @@ cmdoptions = {
'user':'', 'date':'',
'modified':True, 'added':True, 'removed':True, 'deleted':True,
- 'unknown':False, 'ignored':False,
+ 'unknown':True, 'ignored':False,
'exclude':[], 'include':[],
'check': False, 'git':False, 'logfile':'', 'addremove':False,
}
|
@@ -126,8 +126,9 @@ pass
self.update_progress()
if not self.hgthread.isAlive():
+ self._button_stop.set_sensitive(False)
self._button_ok.set_sensitive(True)
- self._button_stop.set_sensitive(False)
+ self._button_ok.grab_focus()
self.returncode = self.hgthread.return_code()
if self.returncode is None:
self.write("\n[command interrupted]")
|
|
|
@@ -1,21 +1,18 @@ #
# hgignore.py - TortoiseHg's dialog for editing .hgignore
#
-# Copyright (C) 2008 Steve Borho <steve@borho.org>
+# Copyright (C) 2008-2009 Steve Borho <steve@borho.org>
#
import os
-import gobject
import gtk
-import pango
-import string
from dialog import *
-import hglib
-from mercurial import hg, ui
+from shlib import shell_notify
+from mercurial import hg, ui, match
class HgIgnoreDialog(gtk.Window):
""" Edit a reposiory .hgignore file """
- def __init__(self, root=''):
+ def __init__(self, root='', fileglob=''):
""" Initialize the Dialog """
gtk.Window.__init__(self, gtk.WINDOW_TOPLEVEL)
@@ -57,6 +54,8 @@ hbox.pack_start(glob_button, False, False, 4)
glob_button.connect('clicked', self.add_glob, glob_entry)
glob_entry.connect('activate', self.add_glob, glob_entry)
+ glob_entry.set_text(fileglob)
+ self.glob_entry = glob_entry
mainvbox.pack_start(hbox, False, False)
hbox = gtk.HBox()
@@ -76,7 +75,10 @@ frame = gtk.Frame('Filters')
hbox.pack_start(frame, True, True, 4)
pattree = gtk.TreeView()
+ pattree.connect('button-press-event', self.tree_button_press)
+ pattree.set_reorderable(False)
sel = pattree.get_selection()
+ sel.set_mode(gtk.SELECTION_SINGLE)
sel.connect("changed", self.pattern_rowchanged)
col = gtk.TreeViewColumn('Patterns', gtk.CellRendererText(), text=0)
pattree.append_column(col)
@@ -88,7 +90,6 @@ self.pattree = pattree
frame.add(scrolledwindow)
-
frame = gtk.Frame('Unknown Files')
hbox.pack_start(frame, True, True, 4)
unknowntree = gtk.TreeView()
@@ -100,9 +101,10 @@ scrolledwindow.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
scrolledwindow.set_border_width(4)
scrolledwindow.add(unknowntree)
- model = gtk.ListStore(gobject.TYPE_STRING)
+ model = gtk.ListStore(str)
unknowntree.set_model(model)
unknowntree.set_headers_visible(False)
+ self.unkmodel = model
frame.add(scrolledwindow)
mainvbox.pack_start(hbox, True, True)
@@ -110,6 +112,35 @@ glob_entry.grab_focus()
self.connect('map_event', self._on_window_map_event)
+ def tree_button_press(self, widget, event):
+ if event.button != 3:
+ return False
+ if event.state & (gtk.gdk.SHIFT_MASK | gtk.gdk.CONTROL_MASK):
+ return False
+
+ path = widget.get_path_at_pos(int(event.x), int(event.y))[0]
+ selection = widget.get_selection()
+ rows = selection.get_selected_rows()
+ if path[0] not in rows[1]:
+ selection.unselect_all()
+ selection.select_path(path[0])
+
+ menu = gtk.Menu()
+ menuitem = gtk.MenuItem('Remove', True)
+ menuitem.connect('activate', self.remove_ignore_line, path[0])
+ menuitem.set_border_width(1)
+ menu.append(menuitem)
+ menu.show_all()
+ menu.popup(None, None, None, 0, 0)
+ return True
+
+ def remove_ignore_line(self, menuitem, linenum):
+ model = self.pattree.get_model()
+ del model[linenum]
+ del self.ignorelines[linenum]
+ self.write_ignore_lines()
+ self.refresh()
+
def pattern_rowchanged(self, sel):
model, iter = sel.get_selected()
if not iter:
@@ -119,43 +150,62 @@ model, iter = sel.get_selected()
if not iter:
return
+ self.glob_entry.set_text(model[iter][0])
def add_glob(self, widget, glob_entry):
- pass
+ newglob = glob_entry.get_text()
+ self.ignorelines.append('glob:' + newglob)
+ self.write_ignore_lines()
+ self.refresh()
- def add_regexp(self, widget, glob_entry):
- pass
+ def add_regexp(self, widget, regexp_entry):
+ newregexp = regexp_entry.get_text()
+ self.ignorelines.append('regexp:' + newregexp)
+ self.write_ignore_lines()
+ self.refresh()
def _on_window_map_event(self, event, param):
- self._refresh_clicked(None)
+ self.refresh()
def _refresh_clicked(self, togglebutton, data=None):
+ self.refresh()
+
+ def refresh(self):
+ try: repo = hg.repository(ui.ui(), path=self.root)
+ except: gtk.main_quit()
+ matcher = match.always(repo.root, repo.root)
+ changes = repo.dirstate.status(matcher, ignored=False, clean=False, unknown=True)
+ (lookup, modified, added, removed, deleted, unknown, ignored, clean) = changes
+ self.unkmodel.clear()
+ for u in unknown:
+ self.unkmodel.append([u])
try:
- l = open(os.path.join(self.root, '.hgignore'), 'rb').readlines()
- if l[0].endswith('\r\n'):
- self.doseoln = True
+ l = open(repo.wjoin('.hgignore'), 'rb').readlines()
+ self.doseoln = l[0].endswith('\r\n')
except IOError, ValueError:
self.doseoln = os.name == 'nt'
l = []
- model = gtk.ListStore(gobject.TYPE_STRING)
- l = [string.strip(line) for line in l]
+ model = gtk.ListStore(str)
+ self.ignorelines = []
for line in l:
- model.append([line])
+ model.append([line.strip()])
+ self.ignorelines.append(line.strip())
self.pattree.set_model(model)
- self.ignorelines = l
+ self.repo = repo
def write_ignore_lines(self):
- if doseoln:
+ if self.doseoln:
out = [line + '\r\n' for line in self.ignorelines]
else:
out = [line + '\n' for line in self.ignorelines]
try:
- f = open(os.path.join(self.root, '.hgignore'), 'wb')
+ f = open(self.repo.wjoin('.hgignore'), 'wb')
f.writelines(out)
f.close()
except IOError:
pass
+ shell_notify(self.repo.wjoin('.hgignore'))
def _close_clicked(self, toolbutton, data=None):
self.destroy()
@@ -177,5 +227,6 @@ gtk.gdk.threads_leave()
if __name__ == "__main__":
+ import hglib
opts = {'root' : hglib.rootpath()}
run(**opts)
|
|
|
@@ -0,0 +1,557 @@ + # shelve.py
+#
+# Copyright 2007 Bryan O'Sullivan <bos@serpentine.com>
+# Copyright 2007 TK Soh <teekaysoh@gmailcom>
+#
+# 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
+import copy, cStringIO, errno, operator, os, re, shutil, tempfile
+
+lines_re = re.compile(r'@@ -(\d+),(\d+) \+(\d+),(\d+) @@\s*(.*)')
+
+def internalpatch(patchobj, ui, strip, cwd, reverse=False, files={}):
+ """use builtin patch to apply <patchobj> to the working directory.
+ returns whether patch was applied with fuzz factor.
+
+ Adapted from patch.internalpatch() to support reverse patching.
+ """
+ try:
+ fp = file(patchobj, 'rb')
+ except TypeError:
+ fp = patchobj
+ if cwd:
+ curdir = os.getcwd()
+ os.chdir(cwd)
+ try:
+ ret = patch.applydiff(ui, fp, files, strip=strip,
+ reverse=reverse)
+ finally:
+ if cwd:
+ os.chdir(curdir)
+ if ret < 0:
+ raise PatchError
+ return ret > 0
+
+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 '<header %s>' % (' '.join(map(repr, self.files())))
+
+ def special(self):
+ for h in self.header:
+ if self.special_re.match(h):
+ return True
+
+ def __cmp__(self, other):
+ return cmp(repr(self), repr(other))
+
+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 write(self, fp):
+ delta = len(self.before) + len(self.after)
+ 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 '<hunk %r@%d>' % (self.filename(), self.fromline)
+
+ def __cmp__(self, other):
+ return cmp(repr(self), repr(other))
+
+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 = data
+
+ 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):
+ 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]
+ 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:
+ r = (ui.prompt(query + _(' [Ynsfdaq?] '), '(?i)[Ynsfdaq?]?$')
+ or 'y').lower()
+ if r == '?':
+ 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 == 's':
+ r = resp_file[0] = 'n'
+ elif r == 'f':
+ r = resp_file[0] = 'y'
+ elif r == 'd':
+ r = resp_all[0] = 'n'
+ elif r == 'a':
+ r = resp_all[0] = 'y'
+ elif r == 'q':
+ raise util.Abort(_('user quit'))
+ return r
+ 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)
+ r = prompt(_('shelve changes to %s?') %
+ _(' and ').join(map(repr, chunk.files())))
+ 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 get_shelve_filename(repo):
+ return repo.join('shelve')
+
+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 shelveing.
+
+ You will be prompted for whether to shelve changes to each
+ modified file, and for files with multiple changes, for each
+ change to use. For each query, 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, shelveing no changes
+
+ ? - display help'''
+
+ if not ui.interactive:
+ raise util.Abort(_('shelve can only be run interactively'))
+
+ forced = opts['force'] or opts['append']
+ if os.path.exists(repo.join('shelve')) 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)
+ 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(fp, ui, 1, repo.root)
+ 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('shelve', "a")
+ else:
+ f = repo.opener('shelve', "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('shelve'))
+ 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)
+ return cmdutil.commit(ui, repo, shelvefunc, pats, opts)
+
+
+def unshelve(ui, repo, *pats, **opts):
+ '''restore shelved changes'''
+
+ try:
+ fp = cStringIO.StringIO()
+ fp.write(repo.opener('shelve').read())
+ if opts['inspect']:
+ ui.status(fp.getvalue())
+ else:
+ files = []
+ for chunk in parsepatch(fp):
+ 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)
+ internalpatch(fp, ui, 1, repo.root)
+ 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('shelve'))
+ 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')),
+ ] + 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')),
+ ],
+ _('hg unshelve [OPTION]... [FILE]...')),
+}
|
|
|
@@ -7,29 +7,29 @@
import os
-import threading
-import StringIO
-import sys
-import shutil
-import tempfile
-import datetime
-import cPickle
+import cStringIO
import pygtk
pygtk.require('2.0')
import gtk
-import gobject
import pango
from mercurial.i18n import _
from mercurial.node import *
-from mercurial import cmdutil, util, ui, hg, commands, patch
+from mercurial import cmdutil, util, ui, hg, commands, patch, mdiff
from mercurial import merge as merge_
-from hgext import extdiff
from shlib import shell_notify
from hglib import toutf, rootpath, gettabwidth
from gdialog import *
from dialog import entry_dialog
+import hgshelve
+
+# diff_model row enumerations
+DM_REJECTED = 0
+DM_CHUNK_TEXT = 1
+DM_NOT_HEADER_CHUNK = 2
+DM_HEADER_CHUNK = 3
+DM_CHUNK_ID = 4
class GStatus(GDialog):
"""GTK+ based dialog for displaying repository status
@@ -168,6 +168,17 @@ self.showdiff_toggle.set_active(False)
self._showdiff_toggled_id = self.showdiff_toggle.connect('toggled', self._showdiff_toggled )
tbuttons.append(self.showdiff_toggle)
+
+ self.shelve_btn = self.make_toolbutton(gtk.STOCK_FILE, 'Shelve',
+ self._shelve_clicked, tip='set aside selected changes')
+ self.unshelve_btn = self.make_toolbutton(gtk.STOCK_EDIT, 'Unshelve',
+ self._unshelve_clicked, tip='restore shelved changes')
+ tbuttons += [
+ gtk.SeparatorToolItem(),
+ self.shelve_btn,
+ self.unshelve_btn,
+ ]
+
return tbuttons
@@ -236,9 +247,6 @@ self.tree.set_reorderable(False)
self.tree.set_enable_search(True)
self.tree.set_search_column(2)
- self.tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
- self.tree.get_selection().connect('changed',
- self._tree_selection_changed, False)
if hasattr(self.tree, 'set_rubber_banding'):
self.tree.set_rubber_banding(True)
self.tree.modify_font(pango.FontDescription(self.fontlist))
@@ -252,7 +260,7 @@ stat_cell = gtk.CellRendererText()
self.selcb = None
- if self.count_revs() < 2:
+ if self.count_revs() < 2 and len(self.repo.changectx(None).parents()) == 1:
col0 = gtk.TreeViewColumn('', toggle_cell)
col0.add_attribute(toggle_cell, 'active', 0)
#col0.set_sort_column_id(0)
@@ -295,11 +303,42 @@ scroller.set_policy(gtk.POLICY_AUTOMATIC, gtk.POLICY_AUTOMATIC)
diff_frame.add(scroller)
- self.diff_text = gtk.TextView()
- self.diff_text.set_wrap_mode(gtk.WRAP_NONE)
- self.diff_text.set_editable(False)
- self.diff_text.modify_font(pango.FontDescription(self.fontdiff))
- scroller.add(self.diff_text)
+ if self.count_revs() == 2 or len(self.repo.changectx(None).parents()) == 1:
+ # use treeview to diff hunks
+ self.diff_model = gtk.ListStore(bool, str, bool, bool, int)
+ self.diff_tree = gtk.TreeView(self.diff_model)
+ self.diff_tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.diff_tree.modify_font(pango.FontDescription(self.fontlist))
+ self.diff_tree.set_headers_visible(False)
+ self.diff_tree.set_property('enable-grid-lines', True)
+ self.diff_tree.connect('row-activated',
+ self._diff_tree_row_act)
+ self.diff_tree.set_enable_search(False)
+
+ diff_hunk_cell = gtk.CellRendererText()
+ diff_hunk_cell.set_property('cell-background', '#EEEEEE')
+ diffcol = gtk.TreeViewColumn('diff', diff_hunk_cell,
+ strikethrough=DM_REJECTED,
+ markup=DM_CHUNK_TEXT,
+ strikethrough_set=DM_NOT_HEADER_CHUNK,
+ cell_background_set=DM_HEADER_CHUNK)
+ diffcol.set_resizable(True)
+ self.diff_tree.append_column(diffcol)
+ self.tree.get_selection().set_mode(gtk.SELECTION_MULTIPLE)
+ self.tree.get_selection().connect('changed',
+ self._tree_selection_changed, False)
+ scroller.add(self.diff_tree)
+ else:
+ # display merge diffs in simple text view
+ self.merge_diff_text = gtk.TextView()
+ self.merge_diff_text.set_wrap_mode(gtk.WRAP_NONE)
+ self.merge_diff_text.set_editable(False)
+ self.merge_diff_text.modify_font(pango.FontDescription(self.fontdiff))
+ self.tree.get_selection().set_mode(gtk.SELECTION_SINGLE)
+ self.tree.get_selection().connect('changed',
+ self._merge_tree_selection_changed, False)
+ self._activate_shelve_buttons(False)
+ scroller.add(self.merge_diff_text)
if self.diffbottom:
self._diffpane = gtk.VPaned()
@@ -378,7 +417,9 @@
def prepare_display(self):
self._ready = True
- self._last_files = []
+ self._last_file = None
+ self._shelve_chunks = []
+ self._filechunks = {}
# If the status load failed, no reason to continue
if not self.reload_status():
raise util.Abort('could not load status')
@@ -410,13 +451,15 @@
matcher = cmdutil.match(self.repo, self.pats, self.opts)
cwd = (self.pats and self.repo.getcwd()) or ''
- modified, added, removed, deleted, unknown, ignored, clean = [
- n for n in self.repo.status(node1=self._node1, node2=self._node2,
+ status = [n for n in self.repo.status(node1=self._node1, node2=self._node2,
match=matcher,
ignored=self.test_opt('ignored'),
clean=self.test_opt('clean'),
unknown=self.test_opt('unknown'))]
+ (modified, added, removed, deleted, unknown, ignored, clean) = status
+ self.modified = modified
+
changetypes = (('modified', 'M', modified),
('added', 'A', added),
('removed', 'R', removed),
@@ -431,7 +474,6 @@ reselect = [self.model[iter][2] for iter in self.tree.get_selection().get_selected_rows()[1]]
# merge-state of files
- from mercurial import merge as merge_
ms = merge_.mergestate(self.repo)
# Load the new data into the tree's model
@@ -443,8 +485,8 @@ for file in changes:
mst = file in ms and ms[file].upper() or ""
file = util.localpath(file)
- self.model.append([file in recheck, char, toutf(file),
- file, mst])
+ checked = file in recheck or char in 'MAR'
+ self.model.append([checked, char, toutf(file), file, mst])
self._update_check_count()
@@ -458,8 +500,14 @@ if not selected:
selection.select_path((0,))
+ files = [row[3] for row in self.model]
+ self._show_diff_hunks(files)
+
self.tree.show()
- self.tree.grab_focus()
+ if hasattr(self, 'text'):
+ self.text.grab_focus()
+ else:
+ self.tree.grab_focus()
return True
@@ -485,10 +533,17 @@
def _select_toggle(self, cellrenderer, path):
+ '''User manually toggled file status'''
self.model[path][0] = not self.model[path][0]
+ self._update_chunk_state(self.model[path])
self._update_check_count()
return True
+ def _update_chunk_state(self, entry):
+ '''Update chunk toggle state to match file toggle state'''
+ file = entry[2]
+ for n in self._filechunks[file][1:]:
+ self.diff_model[n][DM_REJECTED] = not entry[0]
def _show_toggle(self, check, type):
self.opts[type] = check.get_active()
@@ -617,17 +672,19 @@ if success:
self.reload_status()
-
- def _tree_selection_changed(self, selection, force):
- ''' Update the diff text '''
+ def _merge_tree_selection_changed(self, selection, force):
+ ''' Update the diff text with merge diff to both parents'''
def dohgdiff():
- difftext = []
- if len(files) != 0:
- wfiles = [self.repo.wjoin(x) for x in files]
- matcher = cmdutil.match(self.repo, wfiles, self.opts)
- for s in patch.diff(self.repo, self._node1, self._node2, match=matcher,
- opts=patch.diffopts(self.ui, self.opts)):
- difftext.extend(s.splitlines(True))
+ difftext = ['===== Diff to first parent =====\n']
+ wfiles = [self.repo.wjoin(file)]
+ matcher = cmdutil.match(self.repo, wfiles, self.opts)
+ for s in patch.diff(self.repo, self.repo.dirstate.parents()[0], None,
+ match=matcher, opts=patch.diffopts(self.ui, self.opts)):
+ difftext.extend(s.splitlines(True))
+ difftext.append('\n===== Diff to second parent =====\n')
+ for s in patch.diff(self.repo, self.repo.dirstate.parents()[1], None,
+ match=matcher, opts=patch.diffopts(self.ui, self.opts)):
+ difftext.extend(s.splitlines(True))
buffer = gtk.TextBuffer()
buffer.create_tag('removed', foreground='#900000')
@@ -655,27 +712,157 @@ line = line[0] + line[1:].expandtabs(self.tabwidth)
buffer.insert(iter, line)
- self.diff_text.set_buffer(buffer)
+ self.merge_diff_text.set_buffer(buffer)
if self.showdiff_toggle.get_active():
- files = [self.model[iter][3] for iter in self.tree.get_selection().get_selected_rows()[1]]
- if force or files != self._last_files:
- self._last_files = files
+ sel = self.tree.get_selection().get_selected_rows()[1]
+ if not sel:
+ self._last_file = None
+ return False
+ file = self.model[sel[0]][2]
+ if force or file != self._last_file:
+ self._last_file = file
self._hg_call_wrapper('Diff', dohgdiff)
return False
+ def _tree_selection_changed(self, selection, force):
+ if self.showdiff_toggle.get_active():
+ sel = self.tree.get_selection().get_selected_rows()[1]
+ if not sel:
+ self._last_file = None
+ return False
+ file = self.model[sel[0]][2]
+ if force or file != self._last_file:
+ self._last_file = file
+ if file in self._filechunks:
+ row = self._filechunks[file][0]
+ self.diff_tree.scroll_to_cell((row, ), None, True)
+ selection = self.diff_tree.get_selection()
+ selection.unselect_all()
+ selection.select_path((row,))
+ return False
+
+
+ def _diff_tree_row_act(self, tree, path, column):
+ row = self.diff_model[path]
+ chunk = self._shelve_chunks[row[DM_CHUNK_ID]]
+ file = chunk.filename()
+ if file not in self._filechunks: return
+ if row[DM_HEADER_CHUNK]:
+ for n in self._filechunks[file][1:]:
+ self.diff_model[n][DM_REJECTED] = not self.diff_model[n][DM_REJECTED]
+ newvalue = False
+ for n in self._filechunks[file][1:]:
+ if not self.diff_model[n][DM_REJECTED]:
+ newvalue = True
+ break
+ else:
+ row[DM_REJECTED] = not row[DM_REJECTED]
+ if row[DM_REJECTED]:
+ # File has at least one inactive chunk, check others
+ for n in self._filechunks[file][1:]:
+ if not self.diff_model[n][DM_REJECTED]:
+ return
+ newvalue = False
+ else:
+ newvalue = True
+ # Update file's check status
+ for fr in self.model:
+ if fr[2] == file:
+ if fr[0] != newvalue:
+ fr[0] = newvalue
+ self._update_check_count()
+ return
+
+
+ def _show_diff_hunks(self, files):
+ ''' Update the diff text '''
+ def markup(chunk):
+ import cgi
+ hunk = ""
+ chunk.seek(0)
+ lines = chunk.readlines()
+ lines[-1] = lines[-1].strip('\n\r')
+ for line in lines:
+ line = cgi.escape(util.fromlocal(line))
+ if line.startswith('---') or line.startswith('+++'):
+ hunk += '<span foreground="#000090">%s</span>' % line
+ elif line.startswith('-'):
+ hunk += '<span foreground="#900000">%s</span>' % line
+ elif line.startswith('+'):
+ hunk += '<span foreground="#006400">%s</span>' % line
+ elif line.startswith('@@'):
+ hunk = '<span foreground="#FF8000">%s</span>' % line
+ else:
+ hunk += line
+
+ return hunk
+
+ def dohgdiff():
+ self.diff_model.clear()
+ try:
+ difftext = []
+ if len(files) != 0:
+ wfiles = [self.repo.wjoin(x) for x in files]
+ matcher = cmdutil.match(self.repo, wfiles, self.opts)
+ diffopts = mdiff.diffopts(git=True, nodates=True)
+ for s in patch.diff(self.repo, self._node1, self._node2,
+ match=matcher, opts=diffopts):
+ difftext.extend(s.splitlines(True))
+ difftext = cStringIO.StringIO(''.join(difftext))
+ difftext.seek(0)
+
+ self._shelve_chunks = hgshelve.parsepatch(difftext)
+ self._filechunks = {}
+ skip = False
+ for n, chunk in enumerate(self._shelve_chunks):
+ fp = cStringIO.StringIO()
+ chunk.pretty(fp)
+ markedup = markup(fp)
+ isheader = isinstance(chunk, hgshelve.header)
+ if isheader:
+ self._filechunks[chunk.filename()] = [len(self.diff_model)]
+ self.diff_model.append([False, markedup, False, True, n])
+ skip = chunk.special()
+ elif skip != True:
+ self._filechunks[chunk.filename()].append(len(self.diff_model))
+ self.diff_model.append([False, markedup, True, False, n])
+ finally:
+ difftext.close()
+
+ if hasattr(self, 'merge_diff_text'):
+ self.merge_diff_text.set_buffer(gtk.TextBuffer())
+ return
+ self._hg_call_wrapper('Diff', dohgdiff)
+
+ def _has_shelve_file(self):
+ return os.path.exists(self.repo.join('shelve'))
+
+ def _activate_shelve_buttons(self, status):
+ if status:
+ self.shelve_btn.set_sensitive(True)
+ self.unshelve_btn.set_sensitive(self._has_shelve_file())
+ else:
+ self.shelve_btn.set_sensitive(False)
+ self.unshelve_btn.set_sensitive(False)
+
def _showdiff_toggled(self, togglebutton, data=None):
# prevent movement events while setting position
self._diffpane.handler_block(self._diffpane_moved_id)
if togglebutton.get_active():
- self._tree_selection_changed(self.tree.get_selection(), True)
+ if hasattr(self, 'merge_diff_text'):
+ self._merge_tree_selection_changed(self.tree.get_selection(), True)
+ self._activate_shelve_buttons(False)
+ else:
+ self._activate_shelve_buttons(True)
+ self._tree_selection_changed(self.tree.get_selection(), True)
self._diffpane.set_position(self._setting_lastpos)
else:
self._setting_lastpos = self._diffpane.get_position()
self._diffpane.set_position(64000)
- self.diff_text.set_buffer(gtk.TextBuffer())
+ self._activate_shelve_buttons(False)
self._diffpane.handler_unblock(self._diffpane_moved_id)
return True
@@ -690,12 +877,17 @@ sizemax = self._diffpane.get_allocation().width
if self.showdiff_toggle.get_active():
- if paned.get_position() >= sizemax - 55:
+ if paned.get_position() >= sizemax - 55:
self.showdiff_toggle.set_active(False)
- self.diff_text.set_buffer(gtk.TextBuffer())
+ self._activate_shelve_buttons(self.showdiff_toggle.get_active())
elif paned.get_position() < sizemax - 55:
self.showdiff_toggle.set_active(True)
- self._tree_selection_changed(self.tree.get_selection(), True)
+ if hasattr(self, 'merge_diff_text'):
+ self._merge_tree_selection_changed(self.tree.get_selection(), True)
+ self._activate_shelve_buttons(False)
+ else:
+ self._tree_selection_changed(self.tree.get_selection(), True)
+ self._activate_shelve_buttons(True)
self.showdiff_toggle.handler_unblock(self._showdiff_toggled_id)
return False
@@ -785,6 +977,72 @@ shell_notify(wfiles)
self.reload_status()
+ def _shelve_selected(self):
+ # get list of hunks that have not been rejected
+ hlist = [x[DM_CHUNK_ID] for x in self.diff_model if not x[DM_REJECTED]]
+ if not hlist:
+ Prompt('Shelve', 'Please select diff chunks to shelve',
+ self).run()
+ return
+
+ doforce = False
+ doappend = False
+ if self._has_shelve_file():
+ from gtklib import MessageDialog
+ dialog = MessageDialog(flags=gtk.DIALOG_MODAL)
+ dialog.set_title('Shelve')
+ dialog.set_markup('<b>Shelve file exists!</b>')
+ dialog.add_buttons('Overwrite', 1, 'Append', 2, 'Cancel', -1)
+ dialog.set_transient_for(self)
+ rval = dialog.run()
+ dialog.destroy()
+ if rval == -1:
+ return
+ if rval == 1:
+ doforce = True
+ if rval == 2:
+ doappend = True
+
+ # capture the selected hunks to shelve
+ fc = []
+ sc = []
+ for n, c in enumerate(self._shelve_chunks):
+ if isinstance(c, hgshelve.header):
+ if len(fc) > 1 or (len(fc) == 1 and fc[0].binary()):
+ sc += fc
+ fc = [c]
+ elif n in hlist:
+ fc.append(c)
+ if len(fc) > 1 or (len(fc) == 1 and fc[0].binary()):
+ sc += fc
+
+ def filter_patch(ui, chunks):
+ return sc
+
+ # shelve them!
+ self.ui.interactive = True # hgshelve only works 'interactively'
+ opts = {'addremove': None, 'include': [], 'force': doforce,
+ 'append': doappend, 'exclude': []}
+ hgshelve.filterpatch = filter_patch
+ hgshelve.shelve(self.ui, self.repo, **opts)
+ self.reload_status()
+
+ def _unshelve(self):
+ opts = {'addremove': None, 'include': [], 'force': None,
+ 'append': None, 'exclude': [], 'inspect': None}
+ try:
+ hgshelve.unshelve(self.ui, self.repo, **opts)
+ self.reload_status()
+ except:
+ pass
+
+ def _shelve_clicked(self, toolbutton, data=None):
+ self._shelve_selected()
+ self._activate_shelve_buttons(True)
+
+ def _unshelve_clicked(self, toolbutton, data=None):
+ self._unshelve()
+ self._activate_shelve_buttons(True)
def _remove_clicked(self, toolbutton, data=None):
remove_list = self._relevant_files('C')
@@ -852,15 +1110,9 @@
def _ignore_file(self, stat, file):
- ignore = open(self.repo.wjoin('.hgignore'), 'a')
- try:
- try:
- ignore.write('glob:' + util.pconvert(file) + '\n')
- except IOError:
- Prompt('Ignore Failed', 'Could not update .hgignore', self).run()
- finally:
- ignore.close()
- self.reload_status()
+ import hgignore
+ dialog = hgignore.HgIgnoreDialog(self.repo.root, util.pconvert(file))
+ dialog.show_all()
return True
def _mark_resolved(self, stat, file):
@@ -892,7 +1144,9 @@ for entry in self.model:
if ctype and not entry[1] in ctype:
continue
- entry[0] = state
+ if entry[0] != state:
+ entry[0] = state
+ self._update_chunk_state(entry)
self._update_check_count()
@@ -977,12 +1231,12 @@
def run(root='', cwd='', files=[], **opts):
u = ui.ui()
- u.updateopts(debug=False, traceback=False)
+ u.updateopts(debug=False, traceback=False, quiet=True)
repo = hg.repository(u, path=root)
cmdoptions = {
'all':False, 'clean':False, 'ignored':False, 'modified':True,
- 'added':True, 'removed':True, 'deleted':True, 'unknown':False, 'rev':[],
+ 'added':True, 'removed':True, 'deleted':True, 'unknown':True, 'rev':[],
'exclude':[], 'include':[], 'debug':True,'verbose':True,'git':False
}
|
@@ -77,6 +77,9 @@ ('Commit Tool', 'tortoisehg.commit', ['qct', 'internal'],
'Select commit tool launched by TortoiseHg. Qct is'
' not included, must be installed separately'),
+ ('Bottom Diffs', 'gtools.diffbottom', ['False', 'True'],
+ 'Move diff panel below file list in status and '
+ 'commit dialogs. Default: False'),
('Visual Diff Tool', 'tortoisehg.vdiff', [],
'Specify the visual diff tool; must be extdiff command'),
('Visual Editor', 'tortoisehg.editor', [],
|
Loading...