[PATCH RFC] reflog: adds a reflog extension
Durham Goode
durham at fb.com
Wed Oct 1 20:45:45 CDT 2014
# HG changeset patch
# User Durham Goode <durham at fb.com>
# Date 1412200597 25200
# Wed Oct 01 14:56:37 2014 -0700
# Node ID 942be96848993cf7ab5ed529db9c1f39c6d43c30
# Parent 939ce500c92a3dcc0e10815242361ff70a6fcae9
reflog: adds a reflog extension
This adds an extension that tracks the locations of the working copy and
bookmarks over time. It's still a proof of concept, but I wanted to throw
it out there to start the bike shedding early (like finding a better name
than 'reflog'). We're close enough to the release that I don't think it should
go in before that.
Running `hg reflog` by default shows the previous locations of the working
copy (most recent first).
~/myrepo> hg reflog
35a5fcfee452 > rebase -d master
32eee5e2d406 > up .^
b5d6dab4f900 > up foo -C
Specifying a bookmark name shows the locations of that bookmark over time.
~/myrepo> hg reflog foo
d1a696044ec0 > rebase -d master
35a5fcfee452 > rebase -d master
32eee5e2d406 > book foo -f
--date and --user flags exist to show more information about each entry.
~/myrepo> hg reflog foo --user --date
d1a696044ec0 durham 2014-10-01 18:32:14 > rebase -d master
35a5fcfee452 durham 2014-10-01 17:28:54 > rebase -d master
32eee5e2d406 durham 2014-10-01 17:28:30 > book foo -f
It's currently stored as a single .hg/reflog file that is append only. Each
entry can store an arbitrary number of hashes (like storing 2 hashes for a merge
state working copy), which means we could also potentially use this to track
heads in branches as well.
diff --git a/hgext/reflog.py b/hgext/reflog.py
new file mode 100644
--- /dev/null
+++ b/hgext/reflog.py
@@ -0,0 +1,152 @@
+# reflog.py
+#
+# Copyright 2013 Facebook, Inc.
+#
+# This software may be used and distributed according to the terms of the
+# GNU General Public License version 2 or any later version.
+
+from mercurial import util, cmdutil, commands, hg, scmutil, localrepo
+from mercurial import bookmarks, dispatch, dirstate
+from mercurial.extensions import wrapcommand, wrapfunction
+from mercurial.node import nullid, hex
+from mercurial.i18n import _
+import errno, os, getpass, time
+
+cmdtable = {}
+command = cmdutil.command(cmdtable)
+testedwith = 'internal'
+
+bookmarktype = 'bookmark'
+workingcopyparenttype = 'workingcopyparent'
+
+def extsetup(ui):
+ wrapfunction(dispatch, '_parse', recordcommand)
+ wrapfunction(bookmarks.bmstore, 'write', recordbookmarks)
+ wrapfunction(dirstate.dirstate, 'write', recorddirstateparents)
+
+def reposetup(ui, repo):
+ if isinstance(repo, localrepo.localrepository):
+ repo.reflog = Reflog(repo, currentcommand)
+ repo.dirstate.repo = repo
+
+currentcommand = ''
+def recordcommand(orig, ui, args):
+ """Records the current command line args for later logging to the reflog."""
+ global currentcommand
+ currentcommand = ' '.join(args)
+ return orig(ui, args)
+
+def recordbookmarks(orig, self):
+ """Records all bookmark changes to the reflog."""
+ repo = self._repo
+ oldmarks = bookmarks.bmstore(repo)
+ for mark, value in self.iteritems():
+ if value != oldmarks.get(mark):
+ repo.reflog.addentry(bookmarktype, mark, value)
+ return orig(self)
+
+def recorddirstateparents(orig, self):
+ """Records all dirstate parent changes to the reflog."""
+ oldparents = [nullid]
+ try:
+ fp = self._opener("dirstate")
+ st = fp.read(40)
+ fp.close()
+ l = len(st)
+ if l == 40:
+ oldparents = [st[:20]]
+ oldparents.append(st[20:40])
+ except IOError, err:
+ pass
+ if oldparents != self.parents():
+ hashes = [hash for hash in self.parents() if hash != nullid]
+ self.repo.reflog.addentry(workingcopyparenttype, '.', hashes)
+ return orig(self)
+
+ at command('reflog',
+ [('', 'all', None, 'show history for all refs'),
+ ('', 'date', None, 'include timestamp information'),
+ ('', 'user', None, 'include user information'),
+ ], '[OPTION]... [REFNAME]')
+def reflog(ui, repo, *args, **opts):
+ """show the previous position of bookmarks and the working copy
+
+ The reflog is used to see the previous commits that bookmarks and the
+ working copy pointed to. By default it shows the previous locations of the
+ working copy. Passing a bookmark name will show all the previous
+ positions of that bookmark. Passing --all will show the previous
+ locations of all bookmarks and the working copy.
+
+ By default the reflog only shows the commit hash and the command that was
+ running at that time. --date will also show the timestamp of the entry, and
+ --user will show the user name of the user who was executing the command.
+ """
+ refname = '.'
+ if args:
+ refname = args[0]
+ if opts.get('all'):
+ refname = None
+
+ for entry in repo.reflog.iter(refnamecond=refname):
+ timestamp, user, command, reftype, refname, hashes = entry
+ output = ','.join([hash[:12] for hash in hashes])
+ if opts.get('user'):
+ output += ' %s' % user
+ if opts.get('date'):
+ timestruct = time.localtime(timestamp)
+ timestring = time.strftime('%Y-%m-%d %H:%M:%S', timestruct)
+ output += ' %s' % timestring
+ output += ' > %s' % command
+ ui.status('%s\n' % output)
+
+class Reflog(object):
+ def __init__(self, repo, command):
+ self.repo = repo
+ self.command = command
+ self.user = getpass.getuser()
+ self.path = repo.join('reflog')
+
+ def __iter__(self):
+ return self._read()
+
+ def iter(self, reftypecond=None, refnamecond=None):
+ for entry in self._read():
+ time, user, command, reftype, refname, hashes = entry
+ if reftypecond and reftype != reftypecond:
+ continue
+ if refnamecond and refname != refnamecond:
+ continue
+ yield entry
+
+ def _read(self):
+ if not os.path.exists(self.path):
+ raise StopIteration()
+
+ f = open(self.path, 'r')
+ try:
+ raw = f.read()
+ finally:
+ f.close()
+
+ lines = reversed(raw.split('\0'))
+ for line in lines:
+ if not line:
+ continue
+ time, user, command, reftype, refname, hashes = line.split('\n')
+ time = int(time)
+ hashes = hashes.split(',')
+ yield (time, user, command, reftype, refname, hashes)
+
+ def addentry(self, reftype, refname, hashes):
+ if isinstance(hashes, str):
+ hashes = [hashes]
+
+ date = str(int(time.time()))
+ hashes = ','.join([hex(hash) for hash in hashes])
+ data = (date, self.user, self.command, reftype, refname, hashes)
+ data = '\n'.join(data)
+ f = open(self.path, 'a+')
+ try:
+ f.write(data + '\0')
+ finally:
+ f.close()
More information about the Mercurial-devel
mailing list