2 from svn2svn
import svnclient
7 def in_svn(p
, require_in_repo
=False, prefix
=""):
9 Check if a given file/folder is being tracked by Subversion.
10 Prior to SVN 1.6, we could "cheat" and look for the existence of ".svn" directories.
11 With SVN 1.7 and beyond, WC-NG means only a single top-level ".svn" at the root of the working-copy.
12 Use "svn status" to check the status of the file/folder.
14 entries
= svnclient
.status(p
, non_recursive
=True)
18 if require_in_repo
and (d
['status'] == 'added' or d
['revision'] is None):
19 # If caller requires this path to be in the SVN repo, prevent returning True
20 # for paths that are only locally-added.
23 # Don't consider files tracked as deleted in the WC as under source-control.
24 # Consider files which are locally added/copied as under source-control.
25 ret
= True if not (d
['status'] == 'deleted') and (d
['type'] == 'normal' or d
['status'] == 'added' or d
['copied'] == 'true') else False
26 ui
.status(prefix
+ ">> in_svn('%s', require_in_repo=%s) --> %s", p
, str(require_in_repo
), str(ret
), level
=ui
.DEBUG
, color
='GREEN')
29 def is_child_path(path
, p_path
):
30 return True if (path
== p_path
) or (path
.startswith(p_path
+"/")) else False
32 def join_path(base
, child
):
34 return base
+"/"+child
if child
else base
36 def find_svn_ancestors(svn_repos_url
, start_path
, start_rev
, stop_base_path
=None, prefix
=""):
38 Given an initial starting path+rev, walk the SVN history backwards to inspect the
39 ancestry of that path, optionally seeing if it traces back to stop_base_path.
41 Build an array of copyfrom_path and copyfrom_revision pairs for each of the "svn copy"'s.
42 If we find a copyfrom_path which stop_base_path is a substring match of (e.g. we crawled
43 back to the initial branch-copy from trunk), then return the collection of ancestor
44 paths. Otherwise, copyfrom_path has no ancestry compared to stop_base_path.
46 This is useful when comparing "trunk" vs. "branch" paths, to handle cases where a
47 file/folder was renamed in a branch and then that branch was merged back to trunk.
49 'svn_repos_url' is the full URL to the root of the SVN repository,
50 e.g. 'file:///path/to/repo'
51 'start_path' is the path in the SVN repo to the source path to start checking
52 ancestry at, e.g. '/branches/fix1/projectA/file1.txt'.
53 'start_rev' is the revision to start walking the history of start_path backwards from.
54 'stop_base_path' is the path in the SVN repo to stop tracing ancestry once we've reached,
55 i.e. the target path we're trying to trace ancestry back to, e.g. '/trunk'.
57 ui
.status(prefix
+ ">> find_svn_ancestors: Start: (%s) start_path: %s stop_base_path: %s",
58 svn_repos_url
, start_path
+"@"+str(start_rev
), stop_base_path
, level
=ui
.DEBUG
, color
='YELLOW')
63 first_iter_done
= False
66 # Get the first "svn log" entry for cur_path (relative to @cur_rev)
67 ui
.status(prefix
+ ">> find_svn_ancestors: %s", svn_repos_url
+cur_path
+"@"+str(cur_rev
), level
=ui
.DEBUG
, color
='YELLOW')
68 log_entry
= svnclient
.get_first_svn_log_entry(svn_repos_url
+cur_path
, 1, cur_rev
)
70 ui
.status(prefix
+ ">> find_svn_ancestors: Done: no log_entry", level
=ui
.DEBUG
, color
='YELLOW')
73 # If we found a copy-from case which matches our stop_base_path, we're done.
74 # ...but only if we've at least tried to search for the first copy-from path.
75 if stop_base_path
is not None and first_iter_done
and is_child_path(cur_path
, stop_base_path
):
76 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')
79 first_iter_done
= True
80 # Search for any actions on our target path (or parent paths).
81 changed_paths_temp
= []
82 for d
in log_entry
['changed_paths']:
84 if is_child_path(cur_path
, path
):
85 changed_paths_temp
.append({'path': path, 'data': d}
)
86 if not changed_paths_temp
:
87 # If no matches, then we've hit the end of the ancestry-chain.
88 ui
.status(prefix
+ ">> find_svn_ancestors: Done: No matching changed_paths", level
=ui
.DEBUG
, color
='YELLOW')
91 # Reverse-sort any matches, so that we start with the most-granular (deepest in the tree) path.
92 changed_paths
= sorted(changed_paths_temp
, key
=operator
.itemgetter('path'), reverse
=True)
93 # Find the action for our cur_path in this revision. Use a loop to check in reverse order,
94 # so that if the target file/folder is "M" but has a parent folder with an "A" copy-from
95 # then we still correctly match the deepest copy-from.
96 for v
in changed_paths
:
99 # Check action-type for this file
101 if action
not in svnclient
.valid_svn_actions
:
102 raise UnsupportedSVNAction("In SVN rev. %d: action '%s' not supported. Please report a bug!"
103 % (log_entry
['revision'], action
))
104 ui
.status(prefix
+ "> %s %s%s", action
, path
,
105 (" (from %s)" % (d
['copyfrom_path']+"@"+str(d
['copyfrom_revision']))) if d
['copyfrom_path'] else "",
106 level
=ui
.DEBUG
, color
='YELLOW')
108 # If file/folder was deleted, ancestry-chain stops here
111 ui
.status(prefix
+ ">> find_svn_ancestors: Done: deleted", level
=ui
.DEBUG
, color
='YELLOW')
115 # If file/folder was added/replaced but not a copy, ancestry-chain stops here
116 if not d
['copyfrom_path']:
119 ui
.status(prefix
+ ">> find_svn_ancestors: Done: %s with no copyfrom_path",
120 "Added" if action
== "A" else "Replaced",
121 level
=ui
.DEBUG
, color
='YELLOW')
124 # Else, file/folder was added/replaced and is a copy, so add an entry to our ancestors list
125 # and keep checking for ancestors
126 ui
.status(prefix
+ ">> find_svn_ancestors: Found copy-from (action=%s): %s --> %s",
127 action
, path
, d
['copyfrom_path']+"@"+str(d
['copyfrom_revision']),
128 level
=ui
.DEBUG
, color
='YELLOW')
129 ancestors
.append({'path': cur_path
, 'revision': log_entry
['revision'],
130 'copyfrom_path': cur_path
.replace(d
['path'], d
['copyfrom_path']), 'copyfrom_rev': d
['copyfrom_revision']})
131 cur_path
= cur_path
.replace(d
['path'], d
['copyfrom_path'])
132 cur_rev
= d
['copyfrom_revision']
133 # Follow the copy and keep on searching
135 if stop_base_path
and no_ancestry
:
136 # If we're tracing back ancestry to a specific target stop_base_path and
137 # the ancestry-chain stopped before we reached stop_base_path, then return
138 # nothing since there is no ancestry chaining back to that target.
141 if ui
.get_level() >= ui
.DEBUG
:
143 for idx
in range(len(ancestors
)):
145 max_len
= max(max_len
, len(d
['path']+"@"+str(d
['revision'])))
146 ui
.status(prefix
+ ">> find_svn_ancestors: Found parent ancestors:", level
=ui
.DEBUG
, color
='YELLOW_B')
147 for idx
in range(len(ancestors
)):
149 ui
.status(prefix
+ " [%s] %s --> %s", idx
,
150 str(d
['path']+"@"+str(d
['revision'])).ljust(max_len
),
151 str(d
['copyfrom_path']+"@"+str(d
['copyfrom_rev'])),
152 level
=ui
.DEBUG
, color
='YELLOW')
154 ui
.status(prefix
+ ">> find_svn_ancestors: No ancestor-chain found: %s",
155 svn_repos_url
+start_path
+"@"+str(start_rev
), level
=ui
.DEBUG
, color
='YELLOW')