[PATCH 1 of 3 RFC] revert: add support for reverting subrepos

6 views
Skip to first unread message

Angel Ezquerra

unread,
Mar 22, 2012, 6:34:37 PM3/22/12
to mercuri...@selenic.com
# HG changeset patch
# User Angel Ezquerra <angel.e...@gmail.com>
# Date 1332279907 -3600
# Node ID e45677da232ff46f043603fd232a07897f5fb6f2
# Parent ba8b81caee4469a2e47c2d5074c2a59db8ac51ce
revert: add support for reverting subrepos

Reverting a subrepo is done by updating it to the revision that is selected on
the parent repo .hgsubstate file.

* ISSUES/TODO:

- reverting added and revomed subrepos is not supported.

- This patch only allows reverting a subrepo if the --no-backup flag is used,
since no backups are performed on the contents of the subrepo. It could be
possible to add support for backing up the subrepo contents by first performing
a "revert --all" on the subrepo, and then updating the subrepo to the proper
revision.

- The behavior of the --all flag has been changed. It now revers subrepos as well
Note that this may lead to data loss if the user has a dirty subrepo.

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py
@@ -4826,8 +4826,6 @@
if path in names:
return
if path in repo[node].substate:
- ui.warn("%s: %s\n" % (m.rel(path),
- 'reverting subrepos is unsupported'))
return
path_ = path + '/'
for f in names:
@@ -4841,6 +4839,13 @@
if abs not in names:
names[abs] = m.rel(abs), m.exact(abs)

+ targetsubs = [s for s in repo[node].substate if m(s)]
+ if targetsubs and not opts.get('no_backup'):
+ msg = _("cannot revert subrepos unless the no-backup flag is set")
+ hint = _("there are subrepos on the revert list, "
+ "use --no-backup to revert them")
+ raise util.Abort(msg, hint=hint)
+
m = scmutil.matchfiles(repo, names)
changes = repo.status(match=m)[:4]
modified, added, removed, deleted = map(set, changes)
@@ -4969,6 +4974,13 @@
checkout(f)
normal(f)

+ if targetsubs:
+ # Revert the subrepos on the revert list
+ # reverting a subrepo is done by updating it to the revision
+ # specified in the corresponding substate dictionary
+ for sname in targetsubs:
+ ui.status(_('reverting subrepo %s\n') % sname)
+ ctx.sub(sname).get(ctx.substate[sname], overwrite=True)
finally:
wlock.release()

_______________________________________________
Mercurial-devel mailing list
Mercuri...@selenic.com
http://selenic.com/mailman/listinfo/mercurial-devel

Angel Ezquerra

unread,
Mar 22, 2012, 6:34:36 PM3/22/12
to mercuri...@selenic.com
This series adds support for reverting subrepos.

Compared to previous versions of this patch this series adds support for reverting a subrepo even when the --no-backup flag is not set (in which case revert --all) is called on the subrepo before reverting it.

It also adds support for reverting subrepos that have been removed, but not for reverting subrepos that have been added.

I'd like to get comments on the way I'm reverting added subrepos, since I am
editing the .hgsub and .hgsubstate files. If what I did is ok a similar technique could be used to revert added subrepos.

Angel Ezquerra

unread,
Mar 22, 2012, 6:34:38 PM3/22/12
to mercuri...@selenic.com
# HG changeset patch
# User Angel Ezquerra <angel.e...@gmail.com>
# Date 1332366307 -3600
# Node ID 4f3b203e770e8a738d84eb9e006e124718ee4cf5
# Parent e45677da232ff46f043603fd232a07897f5fb6f2
revert: add support for reverting subrepos without --no-backup and/or --all

When a subrepo is reverted but --no-backup is not set, call revert on the
subrepo that is being reverted prior to updating it to the revision specified
in the parent repo's .hgsubstate file.

The --all flag is passed down to the subrepo when it is being reverted. If the
--all flag is not set, all files that are modified on the subrepo will be
reverted.

* NOTES / ISSUES:

- The revert function is shadowed by an existing revert variable. I used
"import commands" and "commands.revert" to access it, but perhaps there is a
better way (e.g. renaming the revert variable).

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py

@@ -4839,12 +4839,8 @@


if abs not in names:
names[abs] = m.rel(abs), m.exact(abs)

+ # get the list of subrepos that must be reverted


targetsubs = [s for s in repo[node].substate if m(s)]

- if targetsubs and not opts.get('no_backup'):
- msg = _("cannot revert subrepos unless the no-backup flag is set")
- hint = _("there are subrepos on the revert list, "
- "use --no-backup to revert them")
- raise util.Abort(msg, hint=hint)



m = scmutil.matchfiles(repo, names)
changes = repo.status(match=m)[:4]

@@ -4976,10 +4972,27 @@

if targetsubs:


# Revert the subrepos on the revert list

- # reverting a subrepo is done by updating it to the revision
- # specified in the corresponding substate dictionary
+ # reverting a subrepo is a 2 step process:
+ # 1. if the no_backup is not set, revert all modified files inside the subrepo
+ # 2. update the subrepo to the revision specified in the corresponding substate dictionary
+
+ # the revert function name is shadowed
+ import commands
+ revert = commands.revert
for sname in targetsubs:


ui.status(_('reverting subrepo %s\n') % sname)

+ if not opts.get('no_backup'):
+ # Revert all files on the subrepo, creating backups
+ # Note that this will not recursively revert subrepos
+ # We could do it if there was a set:subrepos() predicate
+ revert_opts = opts.copy()
+ revert_opts['date'] = None
+ revert_opts['rev'] = ctx.substate[sname][1]
+ if revert_opts['all']:
+ revert(ui, ctx.sub(sname)._repo, **revert_opts)
+ else:
+ revert(ui, ctx.sub(sname)._repo, 'set:modified()', **revert_opts)
+ # Update the repo to the revision specified in the parent repo revision


ctx.sub(sname).get(ctx.substate[sname], overwrite=True)
finally:
wlock.release()

Angel Ezquerra

unread,
Mar 22, 2012, 6:34:39 PM3/22/12
to mercuri...@selenic.com
# HG changeset patch
# User Angel Ezquerra <angel.e...@gmail.com>
# Date 1332370741 -3600
# Node ID 81a9f35cf00ff7b62293bdd3e7cbbdcbc23add55
# Parent 4f3b203e770e8a738d84eb9e006e124718ee4cf5
revert: add support for reverting added subrepos

Detect subrepos that are found on the working directory but not on the parent
revision. This means that they have been added to the .hgsub file (and possibly
to the .hgsubstate file).

To "remove them", parse the .hgsub and .hgsubstate files and remove any lines
that refert to the added subrepo.

NOTES / ISSUES:

* The "command.removesub" function could be folded into the remove command.
* Is modifying the .hgsub and .hgsubstate files automatically OK?
* This may change the line endings on the .hgsub and .hgsubstate files

diff --git a/mercurial/commands.py b/mercurial/commands.py
--- a/mercurial/commands.py
+++ b/mercurial/commands.py

@@ -4606,6 +4606,69 @@

return ret

+def removesub(ui, repo, spath):
+ # remove the corresponding lines from the .hgsub and .hgsubstate files
+ ui.status(_('removing subrepo %s\n') % spath)
+
+ spath = os.path.normcase(spath)
+ hgsubpath = repo.wjoin('.hgsub')
+ hgsubstatepath = repo.wjoin('.hgsubstate')
+
+ try:
+ h = open(hgsubpath, 'r')
+ hgsub = h.readlines()
+ h.close()
+ except:
+ ui.warn(_('could not open .hgsub file for reading'))
+ return
+
+ try:
+ h = open(hgsubstatepath, 'r')
+ hgsubstate = h.readlines()
+ h.close()
+ except:
+ ui.warn(_('could not open .hgsubstate file for reading'))
+ return
+
+ # filter any lines that refer to the selected subrepo
+ def filterlines(lines, filterfunc):
+ filteredlines = []
+ for line in lines:
+ if filterfunc(line):
+ continue
+ filteredlines.append(line)
+ return filteredlines
+
+ def hgsubmatch(line):
+ subpath = os.path.normcase(line.split('=')[0].strip())
+ return subpath == spath
+
+ def hgsubstatematch(line):
+ subpath = line.split()[1].strip()
+ return subpath == spath
+
+ newhgsub = filterlines(hgsub, hgsubmatch)
+ if len(newhgsub) == len(hgsub):
+ ui.warn(_('repository %s is not present on .hgsub file') % spath)
+ return
+
+ # it does not matter if the subrepo is not found on the .hgsubsate file
+ newhgsubstate = filterlines(hgsubstate, hgsubstatematch)
+
+ # Update the .hgsub and .hgsubstate files
+ outdata = [(hgsubpath, newhgsub)]
+ if len(newhgsubstate) != len(hgsubstate):
+ outdata.append((hgsubstatepath, newhgsubstate))
+
+ for fname, lines in outdata:
+ try:
+ h = open(fname, 'w')
+ h.writelines(lines)
+ h.close()
+ except:
+ ui.warn(_('could not open %s file for writing') % fname)
+ return
+
@command('rename|move|mv',
[('A', 'after', None, _('record a rename that has already occurred')),
('f', 'force', None, _('forcibly copy over an existing managed file')),
@@ -4819,12 +4882,15 @@
m.bad = lambda x, y: False
for abs in repo.walk(m):


names[abs] = m.rel(abs), m.exact(abs)

+ wdsubs = [s for s in repo[None].substate if m(s)]

# walk target manifest.

def badfn(path, msg):


if path in names:
return

+ if path in wdsubs:
+ return


if path in repo[node].substate:

return
path_ = path + '/'

@@ -4970,7 +5036,7 @@
checkout(f)
normal(f)

- if targetsubs:
+ if targetsubs or wdsubs:


# Revert the subrepos on the revert list

# reverting a subrepo is a 2 step process:

# 1. if the no_backup is not set, revert all modified files inside the subrepo

@@ -4994,6 +5060,11 @@


revert(ui, ctx.sub(sname)._repo, 'set:modified()', **revert_opts)

# Update the repo to the revision specified in the parent repo revision
ctx.sub(sname).get(ctx.substate[sname], overwrite=True)

+
+ for sname in wdsubs:
+ if sname not in targetsubs:
+ removesub(ui, repo, sname)
+
finally:
wlock.release()

Reply all
Reply to author
Forward
0 new messages