Mercurial > hg > mercurial-crew-with-dirclash
changeset 1033:5426c8248df6
Merge with MPM.
author | Bryan O'Sullivan <bos@serpentine.com> |
---|---|
date | Wed, 24 Aug 2005 12:50:11 -0700 |
parents | 706c590c9060 (diff) 31dcaf9123ba (current diff) |
children | 8dbbea5bc844 |
files | |
diffstat | 5 files changed, 299 insertions(+), 99 deletions(-) [+] |
line wrap: on
line diff
--- a/contrib/mercurial.el +++ b/contrib/mercurial.el @@ -47,6 +47,7 @@ (require 'cl) (require 'diff-mode) (require 'easymenu) +(require 'executable) (require 'vc) @@ -91,6 +92,11 @@ If you want to prevent the commit from p :type 'sexp :group 'mercurial) +(defcustom hg-log-mode-hook nil + "Hook run after a buffer is filled with log information." + :type 'sexp + :group 'mercurial) + (defcustom hg-global-prefix "\C-ch" "The global prefix for Mercurial keymap bindings." :type 'sexp @@ -124,6 +130,20 @@ Set this to nil on platforms with poor p :type 'boolean :group 'mercurial) +(defcustom hg-incoming-repository "default" + "The repository from which changes are pulled from by default. +This should be a symbolic repository name, since it is used for all +repository-related commands." + :type 'string + :group 'mercurial) + +(defcustom hg-outgoing-repository "default-push" + "The repository to which changes are pushed to by default. +This should be a symbolic repository name, since it is used for all +repository-related commands." + :type 'string + :group 'mercurial) + ;;; Other variables. @@ -151,6 +171,7 @@ Set this to nil on platforms with poor p "The name to use for Mercurial output buffers.") (defvar hg-file-history nil) +(defvar hg-repo-history nil) (defvar hg-rev-history nil) @@ -233,6 +254,7 @@ Set this to nil on platforms with poor p (defvar hg-commit-mode-map (make-sparse-keymap)) (define-key hg-commit-mode-map "\C-c\C-c" 'hg-commit-finish) (define-key hg-commit-mode-map "\C-c\C-k" 'hg-commit-kill) +(define-key hg-commit-mode-map "\C-xv=" 'hg-diff-repo) (defvar hg-commit-mode-file-map (make-sparse-keymap)) (define-key hg-commit-mode-file-map @@ -295,6 +317,17 @@ If the command does not exit with a zero (car res)) (cdr res)))) +(defun hg-sync-buffers (path) + "Sync buffers visiting PATH with their on-disk copies. +If PATH is not being visited, but is under the repository root, sync +all buffers visiting files in the repository." + (let ((buf (find-buffer-visiting path))) + (if buf + (with-current-buffer buf + (vc-buffer-sync)) + (hg-do-across-repo path + (vc-buffer-sync))))) + (defun hg-buffer-commands (pnt) "Use the properties of a character to do something sensible." (interactive "d") @@ -358,13 +391,84 @@ Handle frickin' frackin' gratuitous even 'hg-file-history)) path)))) +(defun hg-read-config () + "Return an alist of (key . value) pairs of Mercurial config data. +Each key is of the form (section . name)." + (let (items) + (dolist (line (split-string (hg-chomp (hg-run0 "debugconfig")) "\n") items) + (string-match "^\\([^=]*\\)=\\(.*\\)" line) + (let* ((left (substring line (match-beginning 1) (match-end 1))) + (right (substring line (match-beginning 2) (match-end 2))) + (key (split-string left "\\.")) + (value (hg-replace-in-string right "\\\\n" "\n" t))) + (setq items (cons (cons (cons (car key) (cadr key)) value) items)))))) + +(defun hg-config-section (section config) + "Return an alist of (name . value) pairs for SECTION of CONFIG." + (let (items) + (dolist (item config items) + (when (equal (caar item) section) + (setq items (cons (cons (cdar item) (cdr item)) items)))))) + +(defun hg-string-starts-with (sub str) + "Indicate whether string STR starts with the substring or character SUB." + (if (not (stringp sub)) + (and (> (length str) 0) (equal (elt str 0) sub)) + (let ((sub-len (length sub))) + (and (<= sub-len (length str)) + (string= sub (substring str 0 sub-len)))))) + +(defun hg-complete-repo (string predicate all) + "Attempt to complete a repository name. +We complete on either symbolic names from Mercurial's config or real +directory names from the file system. We do not penalise URLs." + (or (if all + (all-completions string hg-repo-completion-table predicate) + (try-completion string hg-repo-completion-table predicate)) + (let* ((str (expand-file-name string)) + (dir (file-name-directory str)) + (file (file-name-nondirectory str))) + (if all + (let (completions) + (dolist (name (delete "./" (file-name-all-completions file dir)) + completions) + (let ((path (concat dir name))) + (when (file-directory-p path) + (setq completions (cons name completions)))))) + (let ((comp (file-name-completion file dir))) + (if comp + (hg-abbrev-file-name (concat dir comp)))))))) + +(defun hg-read-repo-name (&optional prompt initial-contents default) + "Read the location of a repository." + (save-excursion + (while hg-prev-buffer + (set-buffer hg-prev-buffer)) + (let (hg-repo-completion-table) + (if current-prefix-arg + (progn + (dolist (path (hg-config-section "paths" (hg-read-config))) + (setq hg-repo-completion-table + (cons (cons (car path) t) hg-repo-completion-table)) + (unless (hg-string-starts-with directory-sep-char (cdr path)) + (setq hg-repo-completion-table + (cons (cons (cdr path) t) hg-repo-completion-table)))) + (completing-read (format "Repository%s: " (or prompt "")) + 'hg-complete-repo + nil + nil + initial-contents + 'hg-repo-history + default)) + default)))) + (defun hg-read-rev (&optional prompt default) "Read a revision or tag, offering completions." (save-excursion (while hg-prev-buffer (set-buffer hg-prev-buffer)) (let ((rev (or default "tip"))) - (if (or (not rev) current-prefix-arg) + (if current-prefix-arg (let ((revs (split-string (hg-chomp (hg-run0 "-q" "log" "-r" (format "-%d" @@ -732,8 +836,7 @@ Key bindings modified-files) (unless root (error "Cannot commit outside a repository!")) - (hg-do-across-repo - (vc-buffer-sync)) + (hg-sync-buffers root) (setq modified-files (hg-chomp (hg-run0 "--cwd" root "status" "-arm"))) (when (and (= (length modified-files) 0) (not hg-commit-allow-empty-file-list)) @@ -787,17 +890,21 @@ With a prefix argument, prompt for all o (hg-read-rev " to start with") (let ((rev2 (hg-read-rev " to end with" 'working-dir))) (and (not (eq rev2 'working-dir)) rev2)))) - (unless rev1 - (setq rev1 "-1")) + (hg-sync-buffers path) (let ((a-path (hg-abbrev-file-name path)) + (r1 (or rev1 "tip")) diff) - (hg-view-output ((if (equal rev1 rev2) - (format "Mercurial: Rev %s of %s" rev1 a-path) - (format "Mercurial: Rev %s to %s of %s" - rev1 (or rev2 "Current") a-path))) + (hg-view-output ((cond + ((and (equal r1 "tip") (not rev2)) + (format "Mercurial: Diff against tip of %s" a-path)) + ((equal r1 rev2) + (format "Mercurial: Diff of rev %s of %s" r1 a-path)) + (t + (format "Mercurial: Diff from rev %s to %s of %s" + r1 (or rev2 "Current") a-path)))) (if rev2 - (call-process (hg-binary) nil t nil "diff" "-r" rev1 "-r" rev2 path) - (call-process (hg-binary) nil t nil "diff" "-r" rev1 path)) + (call-process (hg-binary) nil t nil "diff" "-r" r1 "-r" rev2 path) + (call-process (hg-binary) nil t nil "diff" "-r" r1 path)) (diff-mode) (setq diff (not (= (point-min) (point-max)))) (font-lock-fontify-buffer)) @@ -822,32 +929,47 @@ With a prefix argument, prompt for the p (with-current-buffer buf (hg-mode-line))))) -(defun hg-incoming () - (interactive) - (error "not implemented")) +(defun hg-incoming (&optional repo) + "Display changesets present in REPO that are not present locally." + (interactive (list (hg-read-repo-name " where changes would come from"))) + (hg-view-output ((format "Mercurial: Incoming from %s to %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-incoming-repository)))) + (call-process (hg-binary) nil t nil "incoming" + (or repo hg-incoming-repository)) + (hg-log-mode))) (defun hg-init () (interactive) (error "not implemented")) +(defun hg-log-mode () + "Mode for viewing a Mercurial change log." + (goto-char (point-min)) + (when (looking-at "^searching for changes") + (kill-entire-line)) + (run-hooks 'hg-log-mode-hook)) + (defun hg-log (path &optional rev1 rev2) "Display the revision history of PATH, between REV1 and REV2. -REV1 defaults to the initial revision, while REV2 defaults to the tip. -With a prefix argument, prompt for each parameter. -Variable hg-log-limit controls the number of log entries displayed." +REV1 defaults to hg-log-limit changes from the tip revision, while +REV2 defaults to the tip. +With a prefix argument, prompt for each parameter." (interactive (list (hg-read-file-name " to log") (hg-read-rev " to start with" "-1") (hg-read-rev " to end with" (format "-%d" hg-log-limit)))) - (let ((a-path (hg-abbrev-file-name path))) - (hg-view-output ((if (equal rev1 rev2) - (format "Mercurial: Rev %s of %s" rev1 a-path) - (format "Mercurial: Rev %s to %s of %s" - rev1 (or rev2 "Current") a-path))) + (let ((a-path (hg-abbrev-file-name path)) + (r1 (or rev1 (format "-%d" hg-log-limit))) + (r2 (or rev2 rev1 "-1"))) + (hg-view-output ((if (equal r1 r2) + (format "Mercurial: Log of rev %s of %s" rev1 a-path) + (format "Mercurial: Log from rev %s to %s of %s" + r1 r2 a-path))) (if (> (length path) (length (hg-root path))) - (call-process (hg-binary) nil t nil "log" "-r" rev1 "-r" rev2 path) - (call-process (hg-binary) nil t nil "log" "-r" rev1 "-r" rev2)) - (diff-mode) - (font-lock-fontify-buffer)))) + (call-process (hg-binary) nil t nil "log" "-r" r1 "-r" r2 path) + (call-process (hg-binary) nil t nil "log" "-r" r1 "-r" r2)) + (hg-log-mode)))) (defun hg-log-repo (path &optional rev1 rev2) "Display the revision history of the repository containing PATH. @@ -859,17 +981,31 @@ Variable hg-log-limit controls the numbe (hg-read-rev " to end with" (format "-%d" hg-log-limit)))) (hg-log (hg-root path) rev1 rev2)) -(defun hg-outgoing () - (interactive) - (error "not implemented")) +(defun hg-outgoing (&optional repo) + "Display changesets present locally that are not present in REPO." + (interactive (list (hg-read-repo-name " where changes would go to" nil + hg-outgoing-repository))) + (hg-view-output ((format "Mercurial: Outgoing from %s to %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-outgoing-repository)))) + (call-process (hg-binary) nil t nil "outgoing" + (or repo hg-outgoing-repository)) + (hg-log-mode))) (defun hg-pull () (interactive) (error "not implemented")) -(defun hg-push () - (interactive) - (error "not implemented")) +(defun hg-push (&optional repo) + "Push changes to repository REPO." + (interactive (list (hg-read-repo-name " to push to"))) + (hg-view-output ((format "Mercurial: Push from %s to %s" + (hg-abbrev-file-name (hg-root)) + (hg-abbrev-file-name + (or repo hg-outgoing-repository)))) + (call-process (hg-binary) nil t nil "push" + (or repo hg-outgoing-repository)))) (defun hg-revert-buffer-internal () (let ((ctx (hg-buffer-context))) @@ -919,7 +1055,7 @@ prompts for a path to check." (if root (message "The root of this repository is `%s'." root) (message "The path `%s' is not in a Mercurial repository." - (abbreviate-file-name path t)))) + (hg-abbrev-file-name path)))) root) hg-root))
--- a/contrib/patchbomb +++ b/contrib/patchbomb @@ -147,7 +147,8 @@ def patchbomb(ui, repo, *revs, **opts): self.container.append(''.join(self.lines).split('\n')) self.lines = [] - commands.export(ui, repo, *args, **{'output': exportee(patches)}) + commands.export(ui, repo, *args, **{'output': exportee(patches), + 'text': None}) jumbo = [] msgs = []
--- a/mercurial/commands.py +++ b/mercurial/commands.py @@ -35,7 +35,7 @@ def matchpats(repo, cwd, pats = [], opts def makewalk(repo, pats, opts, head = ''): cwd = repo.getcwd() - files, matchfn = matchpats(repo, cwd, pats, opts, head) + files, matchfn, anypats = matchpats(repo, cwd, pats, opts, head) exact = dict(zip(files, files)) def walk(): for src, fn in repo.walk(files = files, match = matchfn): @@ -86,7 +86,7 @@ def revrange(ui, repo, revs, revlog=None for rev in xrange(start, end, step): yield str(rev) else: - yield spec + yield str(fix(spec, None)) def make_filename(repo, r, pat, node=None, total=None, seqno=None, revwidth=None): @@ -193,29 +193,19 @@ def dodiff(fp, ui, repo, node1, node2, f tn = None fp.write(mdiff.unidiff(to, date1, tn, date2, f, r, text=text)) -def show_changeset(ui, repo, rev=0, changenode=None, filelog=None, brinfo=None): +def show_changeset(ui, repo, rev=0, changenode=None, brinfo=None): """show a single changeset or file revision""" - changelog = repo.changelog - if filelog: - log = filelog - filerev = rev - node = filenode = filelog.node(filerev) - changerev = filelog.linkrev(filenode) - changenode = changenode or changelog.node(changerev) - else: - log = changelog - changerev = rev - if changenode is None: - changenode = changelog.node(changerev) - elif not changerev: - rev = changerev = changelog.rev(changenode) - node = changenode + log = repo.changelog + if changenode is None: + changenode = log.node(rev) + elif not rev: + rev = log.rev(changenode) if ui.quiet: - ui.write("%d:%s\n" % (rev, hg.short(node))) + ui.write("%d:%s\n" % (rev, hg.short(changenode))) return - changes = changelog.read(changenode) + changes = log.read(changenode) t, tz = changes[2].split(' ') # a conversion tool was sticking non-integer offsets into repos @@ -226,22 +216,20 @@ def show_changeset(ui, repo, rev=0, chan date = time.asctime(time.localtime(float(t))) + " %+05d" % (int(tz)/-36) parents = [(log.rev(p), ui.verbose and hg.hex(p) or hg.short(p)) - for p in log.parents(node) + for p in log.parents(changenode) if ui.debugflag or p != hg.nullid] if not ui.debugflag and len(parents) == 1 and parents[0][0] == rev-1: parents = [] if ui.verbose: - ui.write("changeset: %d:%s\n" % (changerev, hg.hex(changenode))) + ui.write("changeset: %d:%s\n" % (rev, hg.hex(changenode))) else: - ui.write("changeset: %d:%s\n" % (changerev, hg.short(changenode))) + ui.write("changeset: %d:%s\n" % (rev, hg.short(changenode))) for tag in repo.nodetags(changenode): ui.status("tag: %s\n" % tag) for parent in parents: ui.write("parent: %d:%s\n" % parent) - if filelog: - ui.debug("file rev: %d:%s\n" % (filerev, hg.hex(filenode))) if brinfo and changenode in brinfo: br = brinfo[changenode] @@ -253,7 +241,7 @@ def show_changeset(ui, repo, rev=0, chan ui.status("date: %s\n" % date) if ui.debugflag: - files = repo.changes(changelog.parents(changenode)[0], changenode) + files = repo.changes(log.parents(changenode)[0], changenode) for key, value in zip(["files:", "files+:", "files-:"], files): if value: ui.note("%-12s %s\n" % (key, " ".join(value))) @@ -560,7 +548,8 @@ def commit(ui, repo, *pats, **opts): if not pats and cwd: opts['include'] = [os.path.join(cwd, i) for i in opts['include']] opts['exclude'] = [os.path.join(cwd, x) for x in opts['exclude']] - fns, match = matchpats(repo, (pats and repo.getcwd()) or '', pats, opts) + fns, match, anypats = matchpats(repo, (pats and repo.getcwd()) or '', + pats, opts) if pats: c, a, d, u = repo.changes(files = fns, match = match) files = c + a + [fn for fn in d if repo.dirstate.state(fn) == 'r'] @@ -604,6 +593,13 @@ def debugcheckstate(ui, repo): if errors: raise util.Abort(".hg/dirstate inconsistent with current parent's manifest") +def debugconfig(ui): + try: + repo = hg.repository(ui) + except: pass + for section, name, value in ui.walkconfig(): + ui.write('%s.%s=%s\n' % (section, name, value)) + def debugstate(ui, repo): """show the contents of the current dirstate""" repo.dirstate.read() @@ -842,39 +838,90 @@ def locate(ui, repo, *pats, **opts): else: ui.write(rel, end) -def log(ui, repo, f=None, **opts): - """show the revision history of the repository or a single file""" - if f: - files = relpath(repo, [f]) - filelog = repo.file(files[0]) - log = filelog - lookup = filelog.lookup - else: - files = None - filelog = None - log = repo.changelog - lookup = repo.lookup - revlist = [] - revs = [log.rev(lookup(rev)) for rev in opts['rev']] - while revs: - if len(revs) == 1: - revlist.append(revs.pop(0)) - else: - a = revs.pop(0) - b = revs.pop(0) - off = a > b and -1 or 1 - revlist.extend(range(a, b + off, off)) +def log(ui, repo, *pats, **opts): + """show revision history of entire repository or files""" + # This code most commonly needs to iterate backwards over the + # history it is interested in. This has awful (quadratic-looking) + # performance, so we use iterators that walk forwards through + # windows of revisions, yielding revisions in reverse order, while + # walking the windows backwards. + files, matchfn, anypats = matchpats(repo, repo.getcwd(), pats, opts) + revs = map(int, revrange(ui, repo, opts['rev'] or ['tip:0'])) + wanted = {} + slowpath = anypats + window = 300 + if not slowpath and not files: + # No files, no patterns. Display all revs. + wanted = dict(zip(revs, revs)) + if not slowpath: + # Only files, no patterns. Check the history of each file. + def filerevgen(filelog): + for i in xrange(filelog.count() - 1, 0, -window): + revs = [] + for j in xrange(max(0, i - window), i): + revs.append(filelog.linkrev(filelog.node(j))) + revs.reverse() + for rev in revs: + yield rev - for i in revlist or range(log.count() - 1, -1, -1): - show_changeset(ui, repo, filelog=filelog, rev=i) + minrev, maxrev = min(revs), max(revs) + for filelog in map(repo.file, files): + # A zero count may be a directory or deleted file, so + # try to find matching entries on the slow path. + if filelog.count() == 0: + slowpath = True + break + for rev in filerevgen(filelog): + if rev <= maxrev: + if rev < minrev: break + wanted[rev] = 1 + if slowpath: + # The slow path checks files modified in every changeset. + def mfrevgen(): + for i in xrange(repo.changelog.count() - 1, 0, -window): + for j in xrange(max(0, i - window), i): + yield j, repo.changelog.read(repo.lookup(str(j)))[3] + + for rev, mf in mfrevgen(): + if filter(matchfn, mf): + wanted[rev] = 1 + + def changerevgen(): + class dui: + # Implement and delegate some ui protocol. Save hunks of + # output for later display in the desired order. + def __init__(self, ui): + self.ui = ui + self.hunk = {} + def bump(self, rev): + self.rev = rev + self.hunk[rev] = [] + def status(self, *args): + if not self.quiet: self.write(*args) + def write(self, *args): + self.hunk[self.rev].append(args) + def __getattr__(self, key): + return getattr(self.ui, key) + for i in xrange(0, len(revs), window): + nrevs = [rev for rev in revs[i : min(i + window, len(revs))] + if rev in wanted] + srevs = list(nrevs) + srevs.sort() + du = dui(ui) + for rev in srevs: + du.bump(rev) + yield rev, du + for rev in nrevs: + for args in du.hunk[rev]: + ui.write(*args) + + for rev, dui in changerevgen(): + show_changeset(dui, repo, rev) if opts['patch']: - if filelog: - filenode = filelog.node(i) - i = filelog.linkrev(filenode) - changenode = repo.changelog.node(i) + changenode = repo.changelog.node(rev) prev, other = repo.changelog.parents(changenode) - dodiff(sys.stdout, ui, repo, prev, changenode, files) - ui.write("\n\n") + dodiff(dui, dui, repo, prev, changenode, files) + du.write("\n\n") def manifest(ui, repo, rev=None): """output the latest or given revision of the project manifest""" @@ -1155,7 +1202,7 @@ def status(ui, repo, *pats, **opts): ''' cwd = repo.getcwd() - files, matchfn = matchpats(repo, cwd, pats, opts) + files, matchfn, anypats = matchpats(repo, cwd, pats, opts) (c, a, d, u) = [[util.pathto(cwd, x) for x in n] for n in repo.changes(files=files, match=matchfn)] @@ -1321,6 +1368,7 @@ table = { 'hg commit [OPTION]... [FILE]...'), "copy": (copy, [], 'hg copy SOURCE DEST'), "debugcheckstate": (debugcheckstate, [], 'debugcheckstate'), + "debugconfig": (debugconfig, [], 'debugconfig'), "debugstate": (debugstate, [], 'debugstate'), "debugindex": (debugindex, [], 'debugindex FILE'), "debugindexdot": (debugindexdot, [], 'debugindexdot FILE'), @@ -1370,7 +1418,9 @@ table = { 'hg locate [OPTION]... [PATTERN]...'), "^log|history": (log, - [('r', 'rev', [], 'revision'), + [('I', 'include', [], 'include path in search'), + ('X', 'exclude', [], 'exclude path from search'), + ('r', 'rev', [], 'revision'), ('p', 'patch', None, 'show patch')], 'hg log [-r REV1 [-r REV2]] [-p] [FILE]'), "manifest": (manifest, [], 'hg manifest [REV]'), @@ -1461,7 +1511,7 @@ globalopts = [('v', 'verbose', None, 've ('', 'time', None, 'time how long the command takes'), ] -norepo = "clone init version help debugindex debugindexdot paths" +norepo = "clone init version help debugconfig debugindex debugindexdot paths" def find(cmd): for e in table.keys():
--- a/mercurial/ui.py +++ b/mercurial/ui.py @@ -52,6 +52,17 @@ class ui: return self.cdata.items(section) return [] + def walkconfig(self): + seen = {} + for (section, name), value in self.overlay.iteritems(): + yield section, name, value + seen[section, name] = 1 + for section in self.cdata.sections(): + for name, value in self.cdata.items(section): + if (section, name) in seen: continue + yield section, name, value.replace('\n', '\\n') + seen[section, name] = 1 + def username(self): return (os.environ.get("HGUSER") or self.config("ui", "username") or
--- a/mercurial/util.py +++ b/mercurial/util.py @@ -156,11 +156,13 @@ def matcher(repo, cwd, names, inc, exc, if exc: excmatch = matchfn(map(patkind, exc), '(?:/|$)') - return roots, lambda fn: (incmatch(fn) and not excmatch(fn) and - (fn.endswith('/') or - (not pats and not files) or - (pats and patmatch(fn)) or - (files and filematch(fn)))) + return (roots, + lambda fn: (incmatch(fn) and not excmatch(fn) and + (fn.endswith('/') or + (not pats and not files) or + (pats and patmatch(fn)) or + (files and filematch(fn)))), + (inc or exc or (pats and pats != [('glob', '**')])) and True) def system(cmd, errprefix=None): """execute a shell command that must succeed"""