summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--manifest_xml.py42
-rw-r--r--project.py54
-rw-r--r--subcmds/diffmanifests.py195
-rw-r--r--subcmds/download.py8
4 files changed, 296 insertions, 3 deletions
diff --git a/manifest_xml.py b/manifest_xml.py
index d496337c..3c8fadd6 100644
--- a/manifest_xml.py
+++ b/manifest_xml.py
@@ -32,7 +32,7 @@ else:
32from git_config import GitConfig 32from git_config import GitConfig
33from git_refs import R_HEADS, HEAD 33from git_refs import R_HEADS, HEAD
34from project import RemoteSpec, Project, MetaProject 34from project import RemoteSpec, Project, MetaProject
35from error import ManifestParseError 35from error import ManifestParseError, ManifestInvalidRevisionError
36 36
37MANIFEST_FILE_NAME = 'manifest.xml' 37MANIFEST_FILE_NAME = 'manifest.xml'
38LOCAL_MANIFEST_NAME = 'local_manifest.xml' 38LOCAL_MANIFEST_NAME = 'local_manifest.xml'
@@ -568,10 +568,11 @@ class XmlManifest(object):
568 gitdir = gitdir, 568 gitdir = gitdir,
569 objdir = gitdir, 569 objdir = gitdir,
570 worktree = None, 570 worktree = None,
571 relpath = None, 571 relpath = name or None,
572 revisionExpr = m.revisionExpr, 572 revisionExpr = m.revisionExpr,
573 revisionId = None) 573 revisionId = None)
574 self._projects[project.name] = [project] 574 self._projects[project.name] = [project]
575 self._paths[project.relpath] = project
575 576
576 def _ParseRemote(self, node): 577 def _ParseRemote(self, node):
577 """ 578 """
@@ -845,3 +846,40 @@ class XmlManifest(object):
845 raise ManifestParseError("no %s in <%s> within %s" % 846 raise ManifestParseError("no %s in <%s> within %s" %
846 (attname, node.nodeName, self.manifestFile)) 847 (attname, node.nodeName, self.manifestFile))
847 return v 848 return v
849
850 def projectsDiff(self, manifest):
851 """return the projects differences between two manifests.
852
853 The diff will be from self to given manifest.
854
855 """
856 fromProjects = self.paths
857 toProjects = manifest.paths
858
859 fromKeys = fromProjects.keys()
860 fromKeys.sort()
861 toKeys = toProjects.keys()
862 toKeys.sort()
863
864 diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}
865
866 for proj in fromKeys:
867 if not proj in toKeys:
868 diff['removed'].append(fromProjects[proj])
869 else:
870 fromProj = fromProjects[proj]
871 toProj = toProjects[proj]
872 try:
873 fromRevId = fromProj.GetCommitRevisionId()
874 toRevId = toProj.GetCommitRevisionId()
875 except ManifestInvalidRevisionError:
876 diff['unreachable'].append((fromProj, toProj))
877 else:
878 if fromRevId != toRevId:
879 diff['changed'].append((fromProj, toProj))
880 toKeys.remove(proj)
881
882 for proj in toKeys:
883 diff['added'].append(toProjects[proj])
884
885 return diff
diff --git a/project.py b/project.py
index 73a97812..aa7a49d6 100644
--- a/project.py
+++ b/project.py
@@ -1100,6 +1100,23 @@ class Project(object):
1100 for copyfile in self.copyfiles: 1100 for copyfile in self.copyfiles:
1101 copyfile._Copy() 1101 copyfile._Copy()
1102 1102
1103 def GetCommitRevisionId(self):
1104 """Get revisionId of a commit.
1105
1106 Use this method instead of GetRevisionId to get the id of the commit rather
1107 than the id of the current git object (for example, a tag)
1108
1109 """
1110 if not self.revisionExpr.startswith(R_TAGS):
1111 return self.GetRevisionId(self._allrefs)
1112
1113 try:
1114 return self.bare_git.rev_list(self.revisionExpr, '-1')[0]
1115 except GitError:
1116 raise ManifestInvalidRevisionError(
1117 'revision %s in %s not found' % (self.revisionExpr,
1118 self.name))
1119
1103 def GetRevisionId(self, all_refs=None): 1120 def GetRevisionId(self, all_refs=None):
1104 if self.revisionId: 1121 if self.revisionId:
1105 return self.revisionId 1122 return self.revisionId
@@ -2187,6 +2204,43 @@ class Project(object):
2187 def _allrefs(self): 2204 def _allrefs(self):
2188 return self.bare_ref.all 2205 return self.bare_ref.all
2189 2206
2207 def _getLogs(self, rev1, rev2, oneline=False, color=True):
2208 """Get logs between two revisions of this project."""
2209 comp = '..'
2210 if rev1:
2211 revs = [rev1]
2212 if rev2:
2213 revs.extend([comp, rev2])
2214 cmd = ['log', ''.join(revs)]
2215 out = DiffColoring(self.config)
2216 if out.is_on and color:
2217 cmd.append('--color')
2218 if oneline:
2219 cmd.append('--oneline')
2220
2221 try:
2222 log = GitCommand(self, cmd, capture_stdout=True, capture_stderr=True)
2223 if log.Wait() == 0:
2224 return log.stdout
2225 except GitError:
2226 # worktree may not exist if groups changed for example. In that case,
2227 # try in gitdir instead.
2228 if not os.path.exists(self.worktree):
2229 return self.bare_git.log(*cmd[1:])
2230 else:
2231 raise
2232 return None
2233
2234 def getAddedAndRemovedLogs(self, toProject, oneline=False, color=True):
2235 """Get the list of logs from this revision to given revisionId"""
2236 logs = {}
2237 selfId = self.GetRevisionId(self._allrefs)
2238 toId = toProject.GetRevisionId(toProject._allrefs)
2239
2240 logs['added'] = self._getLogs(selfId, toId, oneline=oneline, color=color)
2241 logs['removed'] = self._getLogs(toId, selfId, oneline=oneline, color=color)
2242 return logs
2243
2190 class _GitGetByExec(object): 2244 class _GitGetByExec(object):
2191 def __init__(self, project, bare, gitdir): 2245 def __init__(self, project, bare, gitdir):
2192 self._project = project 2246 self._project = project
diff --git a/subcmds/diffmanifests.py b/subcmds/diffmanifests.py
new file mode 100644
index 00000000..05998681
--- /dev/null
+++ b/subcmds/diffmanifests.py
@@ -0,0 +1,195 @@
1#
2# Copyright (C) 2014 The Android Open Source Project
3#
4# Licensed under the Apache License, Version 2.0 (the "License");
5# you may not use this file except in compliance with the License.
6# You may obtain a copy of the License at
7#
8# http://www.apache.org/licenses/LICENSE-2.0
9#
10# Unless required by applicable law or agreed to in writing, software
11# distributed under the License is distributed on an "AS IS" BASIS,
12# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13# See the License for the specific language governing permissions and
14# limitations under the License.
15
16from color import Coloring
17from command import PagedCommand
18from manifest_xml import XmlManifest
19
20class _Coloring(Coloring):
21 def __init__(self, config):
22 Coloring.__init__(self, config, "status")
23
24class Diffmanifests(PagedCommand):
25 """ A command to see logs in projects represented by manifests
26
27 This is used to see deeper differences between manifests. Where a simple
28 diff would only show a diff of sha1s for example, this command will display
29 the logs of the project between both sha1s, allowing user to see diff at a
30 deeper level.
31 """
32
33 common = True
34 helpSummary = "Manifest diff utility"
35 helpUsage = """%prog manifest1.xml [manifest2.xml] [options]"""
36
37 helpDescription = """
38The %prog command shows differences between project revisions of manifest1 and
39manifest2. if manifest2 is not specified, current manifest.xml will be used
40instead. Both absolute and relative paths may be used for manifests. Relative
41paths start from project's ".repo/manifests" folder.
42
43The --raw option Displays the diff in a way that facilitates parsing, the
44project pattern will be <status> <path> <revision from> [<revision to>] and the
45commit pattern will be <status> <onelined log> with status values respectively :
46
47 A = Added project
48 R = Removed project
49 C = Changed project
50 U = Project with unreachable revision(s) (revision(s) not found)
51
52for project, and
53
54 A = Added commit
55 R = Removed commit
56
57for a commit.
58
59Only changed projects may contain commits, and commit status always starts with
60a space, and are part of last printed project.
61Unreachable revisions may occur if project is not up to date or if repo has not
62been initialized with all the groups, in which case some projects won't be
63synced and their revisions won't be found.
64
65"""
66
67 def _Options(self, p):
68 p.add_option('--raw',
69 dest='raw', action='store_true',
70 help='Display raw diff.')
71 p.add_option('--no-color',
72 dest='color', action='store_false', default=True,
73 help='does not display the diff in color.')
74
75 def _printRawDiff(self, diff):
76 for project in diff['added']:
77 self.printText("A %s %s" % (project.relpath, project.revisionExpr))
78 self.out.nl()
79
80 for project in diff['removed']:
81 self.printText("R %s %s" % (project.relpath, project.revisionExpr))
82 self.out.nl()
83
84 for project, otherProject in diff['changed']:
85 self.printText("C %s %s %s" % (project.relpath, project.revisionExpr,
86 otherProject.revisionExpr))
87 self.out.nl()
88 self._printLogs(project, otherProject, raw=True, color=False)
89
90 for project, otherProject in diff['unreachable']:
91 self.printText("U %s %s %s" % (project.relpath, project.revisionExpr,
92 otherProject.revisionExpr))
93 self.out.nl()
94
95 def _printDiff(self, diff, color=True):
96 if diff['added']:
97 self.out.nl()
98 self.printText('added projects : \n')
99 self.out.nl()
100 for project in diff['added']:
101 self.printProject('\t%s' % (project.relpath))
102 self.printText(' at revision ')
103 self.printRevision(project.revisionExpr)
104 self.out.nl()
105
106 if diff['removed']:
107 self.out.nl()
108 self.printText('removed projects : \n')
109 self.out.nl()
110 for project in diff['removed']:
111 self.printProject('\t%s' % (project.relpath))
112 self.printText(' at revision ')
113 self.printRevision(project.revisionExpr)
114 self.out.nl()
115
116 if diff['changed']:
117 self.out.nl()
118 self.printText('changed projects : \n')
119 self.out.nl()
120 for project, otherProject in diff['changed']:
121 self.printProject('\t%s' % (project.relpath))
122 self.printText(' changed from ')
123 self.printRevision(project.revisionExpr)
124 self.printText(' to ')
125 self.printRevision(otherProject.revisionExpr)
126 self.out.nl()
127 self._printLogs(project, otherProject, raw=False, color=color)
128 self.out.nl()
129
130 if diff['unreachable']:
131 self.out.nl()
132 self.printText('projects with unreachable revisions : \n')
133 self.out.nl()
134 for project, otherProject in diff['unreachable']:
135 self.printProject('\t%s ' % (project.relpath))
136 self.printRevision(project.revisionExpr)
137 self.printText(' or ')
138 self.printRevision(otherProject.revisionExpr)
139 self.printText(' not found')
140 self.out.nl()
141
142 def _printLogs(self, project, otherProject, raw=False, color=True):
143 logs = project.getAddedAndRemovedLogs(otherProject, oneline=True,
144 color=color)
145 if logs['removed']:
146 removedLogs = logs['removed'].split('\n')
147 for log in removedLogs:
148 if log.strip():
149 if raw:
150 self.printText(' R ' + log)
151 self.out.nl()
152 else:
153 self.printRemoved('\t\t[-] ')
154 self.printText(log)
155 self.out.nl()
156
157 if logs['added']:
158 addedLogs = logs['added'].split('\n')
159 for log in addedLogs:
160 if log.strip():
161 if raw:
162 self.printText(' A ' + log)
163 self.out.nl()
164 else:
165 self.printAdded('\t\t[+] ')
166 self.printText(log)
167 self.out.nl()
168
169 def Execute(self, opt, args):
170 if not args or len(args) > 2:
171 self.Usage()
172
173 self.out = _Coloring(self.manifest.globalConfig)
174 self.printText = self.out.nofmt_printer('text')
175 if opt.color:
176 self.printProject = self.out.nofmt_printer('project', attr = 'bold')
177 self.printAdded = self.out.nofmt_printer('green', fg = 'green', attr = 'bold')
178 self.printRemoved = self.out.nofmt_printer('red', fg = 'red', attr = 'bold')
179 self.printRevision = self.out.nofmt_printer('revision', fg = 'yellow')
180 else:
181 self.printProject = self.printAdded = self.printRemoved = self.printRevision = self.printText
182
183 manifest1 = XmlManifest(self.manifest.repodir)
184 manifest1.Override(args[0])
185 if len(args) == 1:
186 manifest2 = self.manifest
187 else:
188 manifest2 = XmlManifest(self.manifest.repodir)
189 manifest2.Override(args[1])
190
191 diff = manifest1.projectsDiff(manifest2)
192 if opt.raw:
193 self._printRawDiff(diff)
194 else:
195 self._printDiff(diff, color=opt.color)
diff --git a/subcmds/download.py b/subcmds/download.py
index 471e88b5..098d8b43 100644
--- a/subcmds/download.py
+++ b/subcmds/download.py
@@ -18,6 +18,7 @@ import re
18import sys 18import sys
19 19
20from command import Command 20from command import Command
21from error import GitError
21 22
22CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$') 23CHANGE_RE = re.compile(r'^([1-9][0-9]*)(?:[/\.-]([1-9][0-9]*))?$')
23 24
@@ -87,7 +88,12 @@ makes it available in your project's local working directory.
87 for c in dl.commits: 88 for c in dl.commits:
88 print(' %s' % (c), file=sys.stderr) 89 print(' %s' % (c), file=sys.stderr)
89 if opt.cherrypick: 90 if opt.cherrypick:
90 project._CherryPick(dl.commit) 91 try:
92 project._CherryPick(dl.commit)
93 except GitError:
94 print('[%s] Could not complete the cherry-pick of %s' \
95 % (project.name, dl.commit), file=sys.stderr)
96
91 elif opt.revert: 97 elif opt.revert:
92 project._Revert(dl.commit) 98 project._Revert(dl.commit)
93 elif opt.ffonly: 99 elif opt.ffonly: