summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJulien Campergue <julien.campergue@parrot.com>2014-01-09 16:21:37 +0100
committerJulien Campergue <julien.campergue@parrot.com>2014-02-17 11:20:11 +0000
commitdd6542268a59834ecff573de974d788f11af775d (patch)
treee014028b17f04c230bb8de690d701c2b10a1d93e
parentbaca5f7e88e07c86f402ae7423bb3171a33688e3 (diff)
downloadgit-repo-dd6542268a59834ecff573de974d788f11af775d.tar.gz
Add the "diffmanifests" command
This command allows a deeper diff between two manifest projects. In addition to changed projects, it displays the logs of the commits between both revisions for each project. Change-Id: I86d30602cfbc654f8c84db2be5d8a30cb90f1398 Signed-off-by: Julien Campergue <julien.campergue@parrot.com>
-rw-r--r--manifest_xml.py39
-rw-r--r--project.py54
-rw-r--r--subcmds/diffmanifests.py195
3 files changed, 287 insertions, 1 deletions
diff --git a/manifest_xml.py b/manifest_xml.py
index d496337c..3c80d3ce 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'
@@ -845,3 +845,40 @@ class XmlManifest(object):
845 raise ManifestParseError("no %s in <%s> within %s" % 845 raise ManifestParseError("no %s in <%s> within %s" %
846 (attname, node.nodeName, self.manifestFile)) 846 (attname, node.nodeName, self.manifestFile))
847 return v 847 return v
848
849 def projectsDiff(self, manifest):
850 """return the projects differences between two manifests.
851
852 The diff will be from self to given manifest.
853
854 """
855 fromProjects = self.paths
856 toProjects = manifest.paths
857
858 fromKeys = fromProjects.keys()
859 fromKeys.sort()
860 toKeys = toProjects.keys()
861 toKeys.sort()
862
863 diff = {'added': [], 'removed': [], 'changed': [], 'unreachable': []}
864
865 for proj in fromKeys:
866 if not proj in toKeys:
867 diff['removed'].append(fromProjects[proj])
868 else:
869 fromProj = fromProjects[proj]
870 toProj = toProjects[proj]
871 try:
872 fromRevId = fromProj.GetCommitRevisionId()
873 toRevId = toProj.GetCommitRevisionId()
874 except ManifestInvalidRevisionError:
875 diff['unreachable'].append((fromProj, toProj))
876 else:
877 if fromRevId != toRevId:
878 diff['changed'].append((fromProj, toProj))
879 toKeys.remove(proj)
880
881 for proj in toKeys:
882 diff['added'].append(toProjects[proj])
883
884 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)