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)

Reply via email to