Exemple #1
0
def in_svn(p, require_in_repo=False, prefix=""):
    """
    Check if a given file/folder is being tracked by Subversion.
    Prior to SVN 1.6, we could "cheat" and look for the existence of ".svn" directories.
    With SVN 1.7 and beyond, WC-NG means only a single top-level ".svn" at the root of the working-copy.
    Use "svn status" to check the status of the file/folder.
    """
    entries = svnclient.status(p, non_recursive=True)
    if not entries:
        return False
    d = entries[0]
    if require_in_repo and (d["status"] == "added" or d["revision"] is None):
        # If caller requires this path to be in the SVN repo, prevent returning True
        # for paths that are only locally-added.
        ret = False
    else:
        # Don't consider files tracked as deleted in the WC as under source-control.
        # Consider files which are locally added/copied as under source-control.
        ret = (
            True
            if not (d["status"] == "deleted")
            and (d["type"] == "normal" or d["status"] == "added" or d["copied"] == "true")
            else False
        )
    ui.status(
        prefix + ">> in_svn('%s', require_in_repo=%s) --> %s",
        p,
        str(require_in_repo),
        str(ret),
        level=ui.DEBUG,
        color="GREEN",
    )
    return ret
Exemple #2
0
def in_svn(p, require_in_repo=False, prefix=""):
    """
    Check if a given file/folder is being tracked by Subversion.
    Prior to SVN 1.6, we could "cheat" and look for the existence of ".svn" directories.
    With SVN 1.7 and beyond, WC-NG means only a single top-level ".svn" at the root of the working-copy.
    Use "svn status" to check the status of the file/folder.
    """
    entries = svnclient.status(p, non_recursive=True)
    if not entries:
        return False
    d = entries[0]
    if require_in_repo and (d['status'] == 'added' or d['revision'] is None):
        # If caller requires this path to be in the SVN repo, prevent returning True
        # for paths that are only locally-added.
        ret = False
    else:
        # Don't consider files tracked as deleted in the WC as under source-control.
        # Consider files which are locally added/copied as under source-control.
        ret = True if not (d['status'] == 'deleted') and (
            d['type'] == 'normal' or d['status'] == 'added'
            or d['copied'] == 'true') else False
    ui.status(prefix + ">> in_svn('%s', require_in_repo=%s) --> %s",
              p,
              str(require_in_repo),
              str(ret),
              level=ui.DEBUG,
              color='GREEN')
    return ret
Exemple #3
0
def _run_raw_command(cmd, args, fail_if_stderr=False, no_fail=False):
    cmd_string = "%s %s" % (cmd, " ".join(map(shell_quote, args)))
    color = 'BLUE_B'
    if cmd == 'svn' and args[0] in [
            'status', 'st', 'log', 'info', 'list', 'proplist', 'propget',
            'update', 'up', 'cleanup', 'revert'
    ]:
        # Show status-only commands (commands which make no changes to WC) in dim-blue
        color = 'BLUE'
    ui.status("$ %s", cmd_string, level=ui.EXTRA, color=color)
    try:
        pipe = Popen([cmd] + args, executable=cmd, stdout=PIPE, stderr=PIPE)
    except OSError:
        etype, value = sys.exc_info()[:2]
        raise ExternalCommandFailed(
            "Failed running external program: %s\nError: %s" %
            (cmd_string, "".join(traceback.format_exception_only(etype,
                                                                 value))))
    out, err = pipe.communicate()
    if "nothing changed" == out.strip():  # skip this error
        return out
    if (pipe.returncode != 0 or
        (fail_if_stderr and err.strip())) and not no_fail:
        raise ExternalCommandFailed(
            "External program failed (return code %d): %s\n%s\n%s" %
            (pipe.returncode, cmd_string, err, out))
    return out
Exemple #4
0
def _run_raw_shell_command(cmd, no_fail=False):
    ui.status("* %s", cmd, level=ui.EXTRA, color='BLUE')
    st, out = commands.getstatusoutput(cmd)
    if st != 0 and not no_fail:
        raise ExternalCommandFailed(
            "External program failed with non-zero return code (%d): %s\n%s"
            % (st, cmd, out))
    return out
Exemple #5
0
def _run_raw_shell_command(cmd, no_fail=False):
    ui.status("* %s", cmd, level=ui.EXTRA, color='BLUE')
    st, out = commands.getstatusoutput(cmd)
    if st != 0 and not no_fail:
        raise ExternalCommandFailed(
            "External program failed with non-zero return code (%d): %s\n%s" %
            (st, cmd, out))
    return out
Exemple #6
0
def _run_raw_command(cmd, args, fail_if_stderr=False, no_fail=False):
    cmd_string = "%s %s" % (cmd,  " ".join(map(shell_quote, args)))
    color = 'BLUE_B'
    if cmd == 'svn' and args[0] in ['status', 'st', 'log', 'info', 'list', 'proplist', 'propget', 'update', 'up', 'cleanup', 'revert']:
        # Show status-only commands (commands which make no changes to WC) in dim-blue
        color = 'BLUE'
    ui.status("$ %s", cmd_string, level=ui.EXTRA, color=color)
    try:
        pipe = Popen([cmd] + args, executable=cmd, stdout=PIPE, stderr=PIPE)
    except OSError:
        etype, value = sys.exc_info()[:2]
        raise ExternalCommandFailed(
            "Failed running external program: %s\nError: %s"
            % (cmd_string, "".join(traceback.format_exception_only(etype, value))))
    out, err = pipe.communicate()
    if "nothing changed" == out.strip(): # skip this error
        return out
    if (pipe.returncode != 0 or (fail_if_stderr and err.strip())) and not no_fail:
        raise ExternalCommandFailed(
            "External program failed (return code %d): %s\n%s\n%s"
            % (pipe.returncode, cmd_string, err, out))
    return out
Exemple #7
0
def real_main(args):
    global options
    url = args.pop(0)
    ui.status("url: %s", url, level=ui.DEBUG, color='GREEN')
    info = svnclient.info(url)
    repos_root = info['repos_url']
    repos_path = url[len(repos_root):]
    ancestors = find_svn_ancestors(repos_root, repos_path, options.revision)
    if ancestors:
        max_len = 0
        for idx in range(len(ancestors)):
            d = ancestors[idx]
            max_len = max(max_len, len(d['path'] + "@" + str(d['revision'])))
        for idx in range(len(ancestors)):
            d = ancestors[idx]
            ui.status("[%s] %s --> %s",
                      len(ancestors) - idx - 1,
                      str(d['path'] + "@" + str(d['revision'])).ljust(max_len),
                      str(d['copyfrom_path'] + "@" + str(d['copyfrom_rev'])))
    else:
        ui.status("No ancestor-chain found: %s",
                  repos_root + repos_path + "@" + str(options.revision))

    return 0
Exemple #8
0
def find_svn_ancestors(svn_repos_url,
                       start_path,
                       start_rev,
                       stop_base_path=None,
                       prefix=""):
    """
    Given an initial starting path+rev, walk the SVN history backwards to inspect the
    ancestry of that path, optionally seeing if it traces back to stop_base_path.

    Build an array of copyfrom_path and copyfrom_revision pairs for each of the "svn copy"'s.
    If we find a copyfrom_path which stop_base_path is a substring match of (e.g. we crawled
    back to the initial branch-copy from trunk), then return the collection of ancestor
    paths.  Otherwise, copyfrom_path has no ancestry compared to stop_base_path.

    This is useful when comparing "trunk" vs. "branch" paths, to handle cases where a
    file/folder was renamed in a branch and then that branch was merged back to trunk.

    'svn_repos_url' is the full URL to the root of the SVN repository,
      e.g. 'file:///path/to/repo'
    'start_path' is the path in the SVN repo to the source path to start checking
      ancestry at, e.g. '/branches/fix1/projectA/file1.txt'.
    'start_rev' is the revision to start walking the history of start_path backwards from.
    'stop_base_path' is the path in the SVN repo to stop tracing ancestry once we've reached,
      i.e. the target path we're trying to trace ancestry back to, e.g. '/trunk'.
    """
    ui.status(
        prefix +
        ">> find_svn_ancestors: Start: (%s) start_path: %s  stop_base_path: %s",
        svn_repos_url,
        start_path + "@" + str(start_rev),
        stop_base_path,
        level=ui.DEBUG,
        color='YELLOW')
    done = False
    no_ancestry = False
    cur_path = start_path
    cur_rev = start_rev
    first_iter_done = False
    ancestors = []
    while not done:
        # Get the first "svn log" entry for cur_path (relative to @cur_rev)
        ui.status(prefix + ">> find_svn_ancestors: %s",
                  svn_repos_url + cur_path + "@" + str(cur_rev),
                  level=ui.DEBUG,
                  color='YELLOW')
        log_entry = svnclient.get_first_svn_log_entry(svn_repos_url + cur_path,
                                                      1, cur_rev)
        if not log_entry:
            ui.status(prefix + ">> find_svn_ancestors: Done: no log_entry",
                      level=ui.DEBUG,
                      color='YELLOW')
            done = True
            break
        # If we found a copy-from case which matches our stop_base_path, we're done.
        # ...but only if we've at least tried to search for the first copy-from path.
        if stop_base_path is not None and first_iter_done and is_child_path(
                cur_path, stop_base_path):
            ui.status(
                prefix +
                ">> find_svn_ancestors: Done: Found is_child_path(cur_path, stop_base_path) and first_iter_done=True",
                level=ui.DEBUG,
                color='YELLOW')
            done = True
            break
        first_iter_done = True
        # Search for any actions on our target path (or parent paths).
        changed_paths_temp = []
        for d in log_entry['changed_paths']:
            path = d['path']
            if is_child_path(cur_path, path):
                changed_paths_temp.append({'path': path, 'data': d})
        if not changed_paths_temp:
            # If no matches, then we've hit the end of the ancestry-chain.
            ui.status(prefix +
                      ">> find_svn_ancestors: Done: No matching changed_paths",
                      level=ui.DEBUG,
                      color='YELLOW')
            done = True
            continue
        # Reverse-sort any matches, so that we start with the most-granular (deepest in the tree) path.
        changed_paths = sorted(changed_paths_temp,
                               key=operator.itemgetter('path'),
                               reverse=True)
        # Find the action for our cur_path in this revision. Use a loop to check in reverse order,
        # so that if the target file/folder is "M" but has a parent folder with an "A" copy-from
        # then we still correctly match the deepest copy-from.
        for v in changed_paths:
            d = v['data']
            path = d['path']
            # Check action-type for this file
            action = d['action']
            if action not in svnclient.valid_svn_actions:
                raise UnsupportedSVNAction(
                    "In SVN rev. %d: action '%s' not supported. Please report a bug!"
                    % (log_entry['revision'], action))
            ui.status(
                prefix + "> %s %s%s",
                action,
                path,
                (" (from %s)" %
                 (d['copyfrom_path'] + "@" + str(d['copyfrom_revision'])))
                if d['copyfrom_path'] else "",
                level=ui.DEBUG,
                color='YELLOW')
            if action == 'D':
                # If file/folder was deleted, ancestry-chain stops here
                if stop_base_path:
                    no_ancestry = True
                ui.status(prefix + ">> find_svn_ancestors: Done: deleted",
                          level=ui.DEBUG,
                          color='YELLOW')
                done = True
                break
            if action in 'RA':
                # If file/folder was added/replaced but not a copy, ancestry-chain stops here
                if not d['copyfrom_path']:
                    if stop_base_path:
                        no_ancestry = True
                    ui.status(
                        prefix +
                        ">> find_svn_ancestors: Done: %s with no copyfrom_path",
                        "Added" if action == "A" else "Replaced",
                        level=ui.DEBUG,
                        color='YELLOW')
                    done = True
                    break
                # Else, file/folder was added/replaced and is a copy, so add an entry to our ancestors list
                # and keep checking for ancestors
                ui.status(
                    prefix +
                    ">> find_svn_ancestors: Found copy-from (action=%s): %s --> %s",
                    action,
                    path,
                    d['copyfrom_path'] + "@" + str(d['copyfrom_revision']),
                    level=ui.DEBUG,
                    color='YELLOW')
                ancestors.append({
                    'path':
                    cur_path,
                    'revision':
                    log_entry['revision'],
                    'copyfrom_path':
                    cur_path.replace(d['path'], d['copyfrom_path']),
                    'copyfrom_rev':
                    d['copyfrom_revision']
                })
                cur_path = cur_path.replace(d['path'], d['copyfrom_path'])
                cur_rev = d['copyfrom_revision']
                # Follow the copy and keep on searching
                break
    if stop_base_path and no_ancestry:
        # If we're tracing back ancestry to a specific target stop_base_path and
        # the ancestry-chain stopped before we reached stop_base_path, then return
        # nothing since there is no ancestry chaining back to that target.
        ancestors = []
    if ancestors:
        if ui.get_level() >= ui.DEBUG:
            max_len = 0
            for idx in range(len(ancestors)):
                d = ancestors[idx]
                max_len = max(max_len,
                              len(d['path'] + "@" + str(d['revision'])))
            ui.status(prefix +
                      ">> find_svn_ancestors: Found parent ancestors:",
                      level=ui.DEBUG,
                      color='YELLOW_B')
            for idx in range(len(ancestors)):
                d = ancestors[idx]
                ui.status(
                    prefix + " [%s] %s --> %s",
                    idx,
                    str(d['path'] + "@" + str(d['revision'])).ljust(max_len),
                    str(d['copyfrom_path'] + "@" + str(d['copyfrom_rev'])),
                    level=ui.DEBUG,
                    color='YELLOW')
    else:
        ui.status(prefix +
                  ">> find_svn_ancestors: No ancestor-chain found: %s",
                  svn_repos_url + start_path + "@" + str(start_rev),
                  level=ui.DEBUG,
                  color='YELLOW')
    return ancestors
Exemple #9
0
def find_svn_ancestors(svn_repos_url, start_path, start_rev, stop_base_path=None, prefix=""):
    """
    Given an initial starting path+rev, walk the SVN history backwards to inspect the
    ancestry of that path, optionally seeing if it traces back to stop_base_path.

    Build an array of copyfrom_path and copyfrom_revision pairs for each of the "svn copy"'s.
    If we find a copyfrom_path which stop_base_path is a substring match of (e.g. we crawled
    back to the initial branch-copy from trunk), then return the collection of ancestor
    paths.  Otherwise, copyfrom_path has no ancestry compared to stop_base_path.

    This is useful when comparing "trunk" vs. "branch" paths, to handle cases where a
    file/folder was renamed in a branch and then that branch was merged back to trunk.

    'svn_repos_url' is the full URL to the root of the SVN repository,
      e.g. 'file:///path/to/repo'
    'start_path' is the path in the SVN repo to the source path to start checking
      ancestry at, e.g. '/branches/fix1/projectA/file1.txt'.
    'start_rev' is the revision to start walking the history of start_path backwards from.
    'stop_base_path' is the path in the SVN repo to stop tracing ancestry once we've reached,
      i.e. the target path we're trying to trace ancestry back to, e.g. '/trunk'.
    """
    ui.status(
        prefix + ">> find_svn_ancestors: Start: (%s) start_path: %s  stop_base_path: %s",
        svn_repos_url,
        start_path + "@" + str(start_rev),
        stop_base_path,
        level=ui.DEBUG,
        color="YELLOW",
    )
    done = False
    no_ancestry = False
    cur_path = start_path
    cur_rev = start_rev
    first_iter_done = False
    ancestors = []
    while not done:
        # Get the first "svn log" entry for cur_path (relative to @cur_rev)
        ui.status(
            prefix + ">> find_svn_ancestors: %s",
            svn_repos_url + cur_path + "@" + str(cur_rev),
            level=ui.DEBUG,
            color="YELLOW",
        )
        log_entry = svnclient.get_first_svn_log_entry(svn_repos_url + cur_path, 1, cur_rev)
        if not log_entry:
            ui.status(prefix + ">> find_svn_ancestors: Done: no log_entry", level=ui.DEBUG, color="YELLOW")
            done = True
            break
        # If we found a copy-from case which matches our stop_base_path, we're done.
        # ...but only if we've at least tried to search for the first copy-from path.
        if stop_base_path is not None and first_iter_done and is_child_path(cur_path, stop_base_path):
            ui.status(
                prefix
                + ">> find_svn_ancestors: Done: Found is_child_path(cur_path, stop_base_path) and first_iter_done=True",
                level=ui.DEBUG,
                color="YELLOW",
            )
            done = True
            break
        first_iter_done = True
        # Search for any actions on our target path (or parent paths).
        changed_paths_temp = []
        for d in log_entry["changed_paths"]:
            path = d["path"]
            if is_child_path(cur_path, path):
                changed_paths_temp.append({"path": path, "data": d})
        if not changed_paths_temp:
            # If no matches, then we've hit the end of the ancestry-chain.
            ui.status(prefix + ">> find_svn_ancestors: Done: No matching changed_paths", level=ui.DEBUG, color="YELLOW")
            done = True
            continue
        # Reverse-sort any matches, so that we start with the most-granular (deepest in the tree) path.
        changed_paths = sorted(changed_paths_temp, key=operator.itemgetter("path"), reverse=True)
        # Find the action for our cur_path in this revision. Use a loop to check in reverse order,
        # so that if the target file/folder is "M" but has a parent folder with an "A" copy-from
        # then we still correctly match the deepest copy-from.
        for v in changed_paths:
            d = v["data"]
            path = d["path"]
            # Check action-type for this file
            action = d["action"]
            if action not in svnclient.valid_svn_actions:
                raise UnsupportedSVNAction(
                    "In SVN rev. %d: action '%s' not supported. Please report a bug!" % (log_entry["revision"], action)
                )
            ui.status(
                prefix + "> %s %s%s",
                action,
                path,
                (" (from %s)" % (d["copyfrom_path"] + "@" + str(d["copyfrom_revision"]))) if d["copyfrom_path"] else "",
                level=ui.DEBUG,
                color="YELLOW",
            )
            if action == "D":
                # If file/folder was deleted, ancestry-chain stops here
                if stop_base_path:
                    no_ancestry = True
                ui.status(prefix + ">> find_svn_ancestors: Done: deleted", level=ui.DEBUG, color="YELLOW")
                done = True
                break
            if action in "RA":
                # If file/folder was added/replaced but not a copy, ancestry-chain stops here
                if not d["copyfrom_path"]:
                    if stop_base_path:
                        no_ancestry = True
                    ui.status(
                        prefix + ">> find_svn_ancestors: Done: %s with no copyfrom_path",
                        "Added" if action == "A" else "Replaced",
                        level=ui.DEBUG,
                        color="YELLOW",
                    )
                    done = True
                    break
                # Else, file/folder was added/replaced and is a copy, so add an entry to our ancestors list
                # and keep checking for ancestors
                ui.status(
                    prefix + ">> find_svn_ancestors: Found copy-from (action=%s): %s --> %s",
                    action,
                    path,
                    d["copyfrom_path"] + "@" + str(d["copyfrom_revision"]),
                    level=ui.DEBUG,
                    color="YELLOW",
                )
                ancestors.append(
                    {
                        "path": cur_path,
                        "revision": log_entry["revision"],
                        "copyfrom_path": cur_path.replace(d["path"], d["copyfrom_path"]),
                        "copyfrom_rev": d["copyfrom_revision"],
                    }
                )
                cur_path = cur_path.replace(d["path"], d["copyfrom_path"])
                cur_rev = d["copyfrom_revision"]
                # Follow the copy and keep on searching
                break
    if stop_base_path and no_ancestry:
        # If we're tracing back ancestry to a specific target stop_base_path and
        # the ancestry-chain stopped before we reached stop_base_path, then return
        # nothing since there is no ancestry chaining back to that target.
        ancestors = []
    if ancestors:
        if ui.get_level() >= ui.DEBUG:
            max_len = 0
            for idx in range(len(ancestors)):
                d = ancestors[idx]
                max_len = max(max_len, len(d["path"] + "@" + str(d["revision"])))
            ui.status(prefix + ">> find_svn_ancestors: Found parent ancestors:", level=ui.DEBUG, color="YELLOW_B")
            for idx in range(len(ancestors)):
                d = ancestors[idx]
                ui.status(
                    prefix + " [%s] %s --> %s",
                    idx,
                    str(d["path"] + "@" + str(d["revision"])).ljust(max_len),
                    str(d["copyfrom_path"] + "@" + str(d["copyfrom_rev"])),
                    level=ui.DEBUG,
                    color="YELLOW",
                )
    else:
        ui.status(
            prefix + ">> find_svn_ancestors: No ancestor-chain found: %s",
            svn_repos_url + start_path + "@" + str(start_rev),
            level=ui.DEBUG,
            color="YELLOW",
        )
    return ancestors