On 09/27/2016 01:46 AM, Daniel Shahaf wrote:
Johan Corveleyn wrote on Mon, Sep 26, 2016 at 13:04:04 +0200:
Maybe I'm missing something, but I don't understand why 'svn diff
--old=TRUNK --new=BRANCH' would show you things that you previously
merged from TRUNK to BRANCH. It should show exactly the content-wise
difference between TRUNK and BRANCH, so if some content was merged
from TRUNK to BRANCH, both should be identical on that point, and it
shouldn't show up in 'diff'.
That command would also show changes made on trunk that have not yet been
merged to the branch. (E.g., if you ran it in on subversion's trunk and
1.9.x branch, it would show -SVN_VER_MINOR 10\n +SVN_VER_MINOR 9\n.)
The OP asked for the changes merge would do, which is approximately
--old=TRUNK@REV --new=BRANCH
where REV is the youngest revision of trunk merged to the branch.
("Approximately" because this is inaccurate when cherry-picks or subtree
merges hapepned.)
There's one more issue with these approaches. ReviewBoard can be smart
about displaying the moved/copied files. However:
- If one does 'svn merge --reintegrate', Subversion will copy new files
from the branch unchanged, and 'svn diff' will not show them (and hence,
RB won't either) - or, with --show-copies-as-adds, it will show them as
being added anew. For the review purposes, it would be better if instead
of copying the file from the branch unchanged, Subversion would perform
the same move on the trunk and apply the textual changes.
- If you do 'svn diff --old=... --new=...', I believe the copy-from
information is lost from the diff completely - and ReviewBoard will show
all the moved files as adds/deletes, not showing diffs either.
For now, I am using the attached script to perform this task. The
workflow is:
1. Perform a merge to trunk.
2. Run the script (which attempts to find the original location in trunk
for every copied file and replay the move on trunk).
3. 'rbt post'.
The script is not perfect; it only operates at file level - so if there
are renamed directories, the files inside them end up in 'R +' status
(replaced with history) and ReviewBoard shows a spurious deletion for
this file, in addition to a move/copy with changes.
Regards,
Alexey.
#!/usr/bin/python3
# vim: set et sw=4 :
import os
import re
import subprocess
import sys
allowed_paths = [ "/vendor/" ]
debug = False
def get_svninfo_value(svnlog, lookfor):
for l in svnlog.splitlines():
if l.startswith(lookfor):
return l[len(lookfor):]
def get_common_part(f1, f2):
l1 = f1.split('/')
l2 = f2.split('/')
for i in range(0, min(len(l1), len(l2))):
if l1[i] != l2[i]:
break;
else:
i = min(len(l1), len(l2)) + 1
return '/'.join(l1[0:i])
def get_original_path(f):
copied_path = f
rest = ""
wcroot = None
while True:
if copied_path == wcroot:
return f
svnlog = subprocess.check_output(["svn", "info", copied_path],
universal_newlines=True)
if wcroot is None:
wcroot = get_svninfo_value(svnlog, "Working Copy Root Path: ") #
Path to WC
rel_path = get_svninfo_value(svnlog, "Relative URL: ^") # Relative URL
root_url = get_svninfo_value(svnlog, "Repository Root: ") # Repository
root URL
copy_url = get_svninfo_value(svnlog, "Copied From URL: ") # Copy-from
URL
if (rel_path is None or root_url is None):
raise ValueError
if (copy_url is not None):
if (not(copy_url.startswith(root_url + '/'))):
print("Invalid copy URL")
raise ValueError
break
last_slash = copied_path.rindex("/")
rest = copied_path[last_slash:] + rest
copied_path = copied_path[:last_slash]
rel_path += rest
copy_url += rest
svnlog = subprocess.check_output(["svn", "info", wcroot],
universal_newlines=True)
rel_root_path = get_svninfo_value(svnlog, "Relative URL: ^") # Relative URL
for root of WC
if rel_root_path is None:
print("No root path found")
raise ValueError
lookfor = copy_url[len(root_url):]
if debug:
print('wcroot %s' % wcroot)
print("root rel path {%s}" % rel_root_path)
print("look for {%s}" % lookfor)
try:
svnlog = subprocess.check_output(["svn", "log", "-qv", f],
universal_newlines=True)
except subprocess.CalledProcessError:
return f # Ok, even though inside a copied path, this path does not
seem to be copied
logrevs =
svnlog.split("------------------------------------------------------------------------\n")[1:-1]
while True:
if lookfor.startswith(rel_root_path):
orig = wcroot + lookfor[len(rel_root_path):]
if debug:
print("found local copy source for `%s': `%s' (lookfor `%s')" %
(f, orig, lookfor))
return orig
elif get_common_part(lookfor, rel_root_path):
if debug:
print("`%s' copied from `%s', but top of WC (`%s') does not
include that path" % (f, lookfor, rel_root_path))
return None # Change to plain addition
while True:
if len(logrevs) == 0:
if debug:
print("----------------------------")
return None # Suitable copy source not found. Change to addition
r = logrevs.pop(0)
loglines = r.splitlines()
loglines.reverse()
m = None
while len(loglines) != 0:
l = loglines.pop(0)
m = re.match('^ [AR] (?P<dst>.*) \(from
(?P<src>.*):(?P<rev>[0-9]+)\)$', l)
if not m:
continue
copy_src = m.group('src')
copy_dst = m.group('dst')
copy_rev = m.group('rev')
dst_common = get_common_part(copy_dst, lookfor)
if debug:
print("dst_common `%s' copy_src `%s' copy_dst `%s' [%s]" %
(dst_common, copy_src, copy_dst, l))
if dst_common == copy_dst:
break # Includes path of interest
else:
continue # This revision did not touch the path
break # Ok, determine where it came from and figure out what to
look for now
# Replace the copied path with the copy source path
lookfor = copy_src + lookfor[len(copy_dst):]
if debug:
print('==')
print(copy_src)
print(copy_dst)
print(dst_common)
print("look for {%s}" % lookfor)
# Check if it is coming from an explicitly allowed location
for a in allowed_paths:
if lookfor.startswith(a):
src = '^' + lookfor + '@' + copy_rev
if debug:
print("found explicitly allowed source for `%s': `%s'" %
(f, src))
return src
continue # Does not include path of interest
def do_svn_copy(src, dst):
proplist = subprocess.check_output(["svn", "pl", "-q", dst],
universal_newlines=True).splitlines()
props = {}
for p in proplist:
# Property list is indented 2 spaces
p = p[2:]
v = subprocess.check_output(["svn", "pg", p, dst],
universal_newlines=True)
if v[-1] == '\n':
# 'svn pg' prints an extra new line at the end
v = v[:-1]
props[p] = v
os.rename(dst, dst + ".preserve-modified")
subprocess.check_call(["svn", "rm", "--force", dst])
if src:
subprocess.check_call(["svn", "cp", "-q", src, dst])
os.rename(dst + ".preserve-modified", dst)
# Clear copied properties in preparation for restoration belo
proplist = subprocess.check_output(["svn", "pl", "-q", dst],
universal_newlines=True).splitlines()
for p in proplist:
p = p[2:]
subprocess.check_call(["svn", "pd", "-q", p, dst])
else:
os.rename(dst + ".preserve-modified", dst)
subprocess.check_call(["svn", "add", "--no-auto-props", dst])
for p in props:
subprocess.check_call(["svn", "ps", "-q", p, props[p], dst])
def do_copy(src, dst):
if src[0] == '^':
# copy from SVN path
do_svn_copy(src, dst)
else:
# copy from local file - obtain SVN path/revision and reduce to SVN copy
svnlog = subprocess.check_output(["svn", "info", src],
universal_newlines=True)
url = get_svninfo_value(svnlog, "Relative URL: ")
rev = get_svninfo_value(svnlog, "Revision: ")
do_svn_copy(url + '@' + rev, dst)
def handle_path(f):
af = os.path.abspath(f)
orig = get_original_path(af)
if orig is None or os.environ.get('BREAK_HISTORY'):
if os.environ.get('PRESERVE_HISTORY'):
print("Original path not found for `%s'" % f)
else:
print("`%s' <- new file" % (af))
do_svn_copy(None, af)
elif orig != af:
print("`%s' <- `%s'" % (af, orig))
do_copy(orig, af)
else:
print("`%s' not copied" % (af))
if __name__ == "__main__":
for f in sys.argv[1:]:
if os.path.isdir(f):
# Checking status of each file would be gruesomely long process.
Quickly check
# `svn st' to get the list of changes - then handle each copied
file and recurse into
# each copied directory
status = subprocess.check_output(["svn", "st", f],
universal_newlines=True)
for l in status.splitlines():
if len(l) < 4 or l[3] != "+":
continue
path = l[8:]
if os.path.isdir(path):
for c, ds, fs in os.walk(path):
for f in fs:
handle_path(os.path.join(c, f))
else:
handle_path(path)
else:
handle_path(f)