Skip to content

Commit 8ff45ed

Browse files
committed
Merge branch 'develop'
2 parents 94b166a + 127d9ad commit 8ff45ed

File tree

11 files changed

+308
-40
lines changed

11 files changed

+308
-40
lines changed

.gitignore

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@
22
config.ini
33
Compare_*.txt
44
StreamComponents_*.txt
5-
accept*.txt
5+
accept*.txt
6+
History_B*.txt
7+
__pycache__
8+
*.swp
9+
*.pyc
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
_SOMERANDOMUUID
2+
_SOMEOTHERANDOMUUID
3+
_ANDSOONANOTHERUUID
4+
_OLDESTUUID

README.md

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,9 @@ It uses the CLI of RTC to gather the required informations (You can find the CLI
1010
</ul>
1111

1212
## Usage
13-
<ul>
14-
<li>Create a config file called "config.ini" and fill out the needed informations, use the supplied "config.ini.sample" as reference</li>
15-
<li>Execute migration.py</li>
16-
</ul>
13+
- Create a config file called "config.ini" and fill out the needed informations, use the supplied "config.ini.sample" as reference
14+
- Execute migration.py
15+
1716

1817
## How does it work?
1918
<ol>

config.ini.sample

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,3 +22,13 @@ OldestStream = Stream_Version1
2222
# (baseline which were created earlier than the baselines of the oldest stream)
2323
# Use following format: ComponentName = BaseLineName, AnotherComponentName=BaseLineName
2424
InitialBaseLines =
25+
26+
# False - Rely on order of changeset provided by the rtc cli compare command (due wrong order, more likely to cause merge-conflicts
27+
# True - (Component)History needs to be provided in a separate file by the user
28+
# For more information read https://github.com/WtfJoke/rtc2git/wiki/Getting-your-History-Files
29+
UseProvidedHistory = False
30+
31+
32+
[Miscellaneous]
33+
# Set to true if you want to see which commands are sent to command line
34+
LogShellCommands = False

configuration.py

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,9 +32,11 @@ def read():
3232
baseline = componentbaseline[1].strip()
3333
initialcomponentbaselines.append(ComponentBaseLineEntry(component, baseline, component, baseline))
3434
gitreponame = generalsection['GIT-Reponame']
35+
useprovidedhistory = migrationsection['UseProvidedHistory']
36+
shell.logcommands = config['Miscellaneous']['LogShellCommands'] == "True"
3537
return ConfigObject(user, password, repositoryurl, workspace, useexistingworkspace, workdirectory,
3638
initialcomponentbaselines, streamnames,
37-
gitreponame, oldeststream)
39+
gitreponame, oldeststream, useprovidedhistory)
3840

3941

4042
def getstreamnames(streamsfromconfig):
@@ -48,18 +50,20 @@ def getstreamnames(streamsfromconfig):
4850
class ConfigObject:
4951
def __init__(self, user, password, repo, workspace, useexistingworkspace, workdirectory, initialcomponentbaselines,
5052
streamnames,
51-
gitreponame, oldeststream):
53+
gitreponame, oldeststream, useprovidedhistory):
5254
self.user = user
5355
self.password = password
5456
self.repo = repo
5557
self.workspace = workspace
56-
self.useexistingworkspace = useexistingworkspace is "True"
58+
self.useexistingworkspace = useexistingworkspace == "True"
59+
self.useprovidedhistory = useprovidedhistory == "True"
5760
self.workDirectory = workdirectory
5861
self.initialcomponentbaselines = initialcomponentbaselines
5962
self.streamnames = streamnames
6063
self.earlieststreamname = oldeststream
6164
self.gitRepoName = gitreponame
6265
self.clonedGitRepoName = gitreponame[:-4] # cut .git
66+
self.rootFolder = os.getcwd()
6367
self.logFolder = os.getcwd() + os.sep + "Logs"
6468
self.hasCreatedLogFolder = os.path.exists(self.logFolder)
6569
self.streamuuids = []
@@ -70,6 +74,10 @@ def getlogpath(self, filename):
7074
self.hasCreatedLogFolder = True
7175
return self.logFolder + os.sep + filename
7276

77+
def gethistorypath(self, filename):
78+
historypath = self.rootFolder + os.sep + "History"
79+
return historypath + os.sep + filename
80+
7381
def collectstreamuuids(self):
7482
shouter.shout("Get UUID's of configured streamnames")
7583
for streamname in self.streamnames:

gitFunctions.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -42,18 +42,21 @@ class Commiter:
4242

4343
@staticmethod
4444
def addandcommit(changeentry):
45-
comment = Commiter.replacegitcreatingfilesymbol(changeentry.comment)
4645
Commiter.replaceauthor(changeentry.author, changeentry.email)
4746
shell.execute("git add -A")
48-
shell.execute("git commit -m %s --date %s --author=%s"
49-
% (shell.quote(comment), shell.quote(changeentry.date), changeentry.getgitauthor()))
47+
shell.execute(Commiter.getcommitcommand(changeentry))
5048
Commiter.commitcounter += 1
5149
if Commiter.commitcounter is 30:
5250
shouter.shout("30 Commits happend, push current branch to avoid out of memory")
5351
Commiter.pushbranch("")
5452
Commiter.commitcounter = 0
5553
shouter.shout("Commited change in local git repository")
5654

55+
@staticmethod
56+
def getcommitcommand(changeentry):
57+
comment = Commiter.replacegitcreatingfilesymbol(changeentry.comment)
58+
return "git commit -m %s --date %s --author=%s" \
59+
% (shell.quote(comment), shell.quote(changeentry.date), changeentry.getgitauthor())
5760

5861
@staticmethod
5962
def replacegitcreatingfilesymbol(comment):

migration.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -41,16 +41,20 @@ def migrate():
4141
componentbaselineentries = rtc.getcomponentbaselineentriesfromstream(streamuuid)
4242
streamname = config.streamnames[streamuuids.index(streamuuid)]
4343
rtcworkspace.setnewflowtargets(streamuuid)
44-
4544
git.branch(streamname)
46-
rtc.acceptchangesintoworkspace(rtc.getchangeentriesofstreamcomponents(componentbaselineentries))
45+
46+
history = rtc.readhistory(componentbaselineentries, streamname)
47+
changeentries = rtc.getchangeentriesofstreamcomponents(componentbaselineentries)
48+
49+
rtc.acceptchangesintoworkspace(rtc.getchangeentriestoaccept(changeentries, history))
4750
shouter.shout("All changes of components of stream '%s' accepted" % streamname)
4851
git.pushbranch(streamname)
4952

5053
rtcworkspace.setcomponentstobaseline(componentbaselineentries, streamuuid)
5154
rtcworkspace.load()
5255

53-
rtc.acceptchangesintoworkspace(rtc.getchangeentriesofstream(streamuuid))
56+
changeentries = rtc.getchangeentriesofstream(streamuuid)
57+
rtc.acceptchangesintoworkspace(rtc.getchangeentriestoaccept(changeentries, history))
5458
git.pushbranch(streamname)
5559
shouter.shout("All changes of stream '%s' accepted - Migration of stream completed" % streamname)
5660

rtcFunctions.py

Lines changed: 150 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import sys
2+
import os
23

4+
import sorter
35
import shell
46
from gitFunctions import Commiter
57
import shouter
@@ -39,8 +41,9 @@ def createandload(self, stream, componentbaselineentries=[], create=True):
3941
self.load()
4042

4143
def load(self):
42-
shouter.shout("Start (re)loading current workspace")
43-
shell.execute("lscm load -r %s %s --force" % (self.repo, self.workspace))
44+
command = "lscm load -r %s %s --force" % (self.repo, self.workspace)
45+
shouter.shout("Start (re)loading current workspace: " + command)
46+
shell.execute(command)
4447
shouter.shout("Load of workspace finished")
4548

4649
def setcomponentstobaseline(self, componentbaselineentries, streamuuid):
@@ -54,14 +57,14 @@ def setcomponentstobaseline(self, componentbaselineentries, streamuuid):
5457
def setnewflowtargets(self, streamuuid):
5558
shouter.shout("Set new Flowtargets")
5659
if not self.hasflowtarget(streamuuid):
57-
shell.execute("lscm add flowtarget -r %s %s %s"
58-
% (self.repo, self.workspace, streamuuid))
59-
shell.execute("lscm set flowtarget -r %s %s --default --current %s"
60-
% (self.repo, self.workspace, streamuuid))
60+
shell.execute("lscm add flowtarget -r %s %s %s" % (self.repo, self.workspace, streamuuid))
61+
62+
command = "lscm set flowtarget -r %s %s --default --current %s" % (self.repo, self.workspace, streamuuid)
63+
shell.execute(command)
6164

6265
def hasflowtarget(self, streamuuid):
63-
flowtargetlines = shell.getoutput("lscm --show-uuid y --show-alias n list flowtargets -r %s %s"
64-
% (self.repo, self.workspace))
66+
command = "lscm --show-uuid y --show-alias n list flowtargets -r %s %s" % (self.repo, self.workspace)
67+
flowtargetlines = shell.getoutput(command)
6568
for flowtargetline in flowtargetlines:
6669
splittedinformationline = flowtargetline.split("\"")
6770
uuidpart = splittedinformationline[0].split(" ")
@@ -74,14 +77,39 @@ def recreateoldestworkspace(self):
7477
self.createandload(self.config.earlieststreamname, self.config.initialcomponentbaselines, False)
7578

7679

80+
class Changes:
81+
latest_accept_command = ""
82+
83+
@staticmethod
84+
def discard(*changeentries):
85+
idstodiscard = Changes._collectids(changeentries)
86+
shell.execute("lscm discard --overwrite-uncommitted " + idstodiscard)
87+
88+
@staticmethod
89+
def accept(*changeentries, logpath):
90+
for changeEntry in changeentries:
91+
shouter.shout("Accepting: " + changeEntry.tostring())
92+
revisions = Changes._collectids(changeentries)
93+
latest_accept_command = "lscm accept -v --overwrite-uncommitted --changes " + revisions
94+
return shell.execute(latest_accept_command, logpath, "a")
95+
96+
@staticmethod
97+
def _collectids(changeentries):
98+
ids = ""
99+
for changeentry in changeentries:
100+
ids += " " + changeentry.revision
101+
return ids
102+
103+
77104
class ImportHandler:
78105
def __init__(self, config):
79106
self.config = config
107+
self.acceptlogpath = config.getlogpath("accept.txt")
80108

81109
def getcomponentbaselineentriesfromstream(self, stream):
82110
filename = self.config.getlogpath("StreamComponents_" + stream + ".txt")
83-
shell.execute(
84-
"lscm --show-alias n --show-uuid y list components -v -r " + self.config.repo + " " + stream, filename)
111+
command = "lscm --show-alias n --show-uuid y list components -v -r " + self.config.repo + " " + stream
112+
shell.execute(command, filename)
85113
componentbaselinesentries = []
86114
skippedfirstrow = False
87115
islinewithcomponent = 2
@@ -120,33 +148,105 @@ def acceptchangesintoworkspace(self, changeentries):
120148
amountofchanges = len(changeentries)
121149
shouter.shoutwithdate("Start accepting %s changesets" % amountofchanges)
122150
amountofacceptedchanges = 0
151+
skipnextchangeset = False
152+
reloaded = False
123153
for changeEntry in changeentries:
124154
amountofacceptedchanges += 1
125-
revision = changeEntry.revision
126-
acceptingmsg = "Accepting: " + changeEntry.comment + " (Date: " + changeEntry.date + " Author: " \
127-
+ changeEntry.author + " Revision: " + revision + ")"
128-
shouter.shout(acceptingmsg)
129-
acceptcommand = "lscm accept --changes " + revision + " --overwrite-uncommitted"
130-
acceptedsuccesfully = shell.execute(acceptcommand, self.config.getlogpath("accept.txt"), "a") is 0
155+
if skipnextchangeset:
156+
skipnextchangeset = False
157+
continue
158+
acceptedsuccesfully = Changes.accept(changeEntry, logpath=self.acceptlogpath) is 0
131159
if not acceptedsuccesfully:
132-
shouter.shout("Last executed command: " + acceptcommand)
133-
sys.exit("Change wasnt succesfully accepted into workspace, please check the output and "
134-
"rerun programm with resume")
160+
shouter.shout("Change wasnt succesfully accepted into workspace")
161+
skipnextchangeset = self.retryacceptincludingnextchangeset(changeEntry, changeentries)
162+
elif not reloaded:
163+
if self.is_reloading_necessary():
164+
WorkspaceHandler(self.config).load()
165+
reloaded = True
135166
shouter.shout("Accepted change %s/%s into working directory" % (amountofacceptedchanges, amountofchanges))
136167
git.addandcommit(changeEntry)
137168

169+
@staticmethod
170+
def is_reloading_necessary():
171+
return shell.execute("git diff --exit-code") is 0
172+
173+
def retryacceptincludingnextchangeset(self, change, changes):
174+
successfull = False
175+
nextchangeentry = self.getnextchangeset(change, changes)
176+
if nextchangeentry and (change.author == nextchangeentry.author or "merge" in nextchangeentry.comment.lower()):
177+
shouter.shout("Next changeset: " + nextchangeentry.tostring())
178+
if input("Press Enter to try to accept it with next changeset together, press any other key to skip this"
179+
" changeset and continue"):
180+
return False
181+
Changes.discard(change)
182+
successfull = Changes.accept(change, nextchangeentry, logpath=self.acceptlogpath) is 0
183+
if not successfull:
184+
Changes.discard(change, nextchangeentry)
185+
186+
if not successfull:
187+
shouter.shout("Last executed command: \n" + Changes.latest_accept_command)
188+
shouter.shout("Apropriate git commit command \n" + Commiter.getcommitcommand(change))
189+
if not input("Press Enter to continue or any other key to exit the program and rerun it with resume"):
190+
sys.exit("Please check the output and rerun programm with resume")
191+
return successfull
192+
193+
@staticmethod
194+
def getnextchangeset(currentchangeentry, changeentries):
195+
nextchangeentry = None
196+
nextindex = changeentries.index(currentchangeentry) + 1
197+
has_next_changeset = nextindex is not len(changeentries)
198+
if has_next_changeset:
199+
nextchangeentry = changeentries[nextindex]
200+
return nextchangeentry
201+
138202
def getchangeentriesofstreamcomponents(self, componentbaselineentries):
203+
missingchangeentries = {}
139204
shouter.shout("Start collecting changeentries")
140-
changeentries = []
205+
changeentriesbycomponentbaselineentry = {}
141206
for componentBaseLineEntry in componentbaselineentries:
142-
changeentries.extend(self.getchangeentriesofbaseline(componentBaseLineEntry.baseline))
143-
changeentries.sort(key=lambda change: change.date)
144-
return changeentries
207+
changeentries = self.getchangeentriesofbaseline(componentBaseLineEntry.baseline)
208+
for changeentry in changeentries:
209+
missingchangeentries[changeentry.revision] = changeentry
210+
return missingchangeentries
211+
212+
def readhistory(self, componentbaselineentries, streamname):
213+
if not self.config.useprovidedhistory:
214+
warning = "Warning - UseProvidedHistory is set to false, merge-conflicts are more likely to happen. \n " \
215+
"For more information see https://github.com/WtfJoke/rtc2git/wiki/Getting-your-History-Files"
216+
shouter.shout(warning)
217+
return None
218+
historyuuids = {}
219+
shouter.shout("Start reading history files")
220+
for componentBaseLineEntry in componentbaselineentries:
221+
history = self.gethistory(componentBaseLineEntry.componentname, streamname)
222+
historyuuids[componentBaseLineEntry.component] = history
223+
return historyuuids
224+
225+
@staticmethod
226+
def getchangeentriestoaccept(missingchangeentries, history):
227+
changeentriestoaccept = []
228+
if history:
229+
historywithchangeentryobject = {}
230+
for key in history.keys():
231+
currentuuids = history.get(key)
232+
changeentries = []
233+
for uuid in currentuuids:
234+
changeentry = missingchangeentries.get(uuid)
235+
if changeentry:
236+
changeentries.append(changeentry)
237+
historywithchangeentryobject[key] = changeentries
238+
changeentriestoaccept = sorter.tosortedlist(historywithchangeentryobject)
239+
else:
240+
changeentriestoaccept.extend(missingchangeentries.values())
241+
# simple sort by date - same as returned by compare command
242+
changeentriestoaccept.sort(key=lambda change: change.date)
243+
return changeentriestoaccept
145244

146245
@staticmethod
147246
def getchangeentriesfromfile(outputfilename):
148247
informationseparator = "@@"
149248
changeentries = []
249+
150250
with open(outputfilename, 'r') as file:
151251
for line in file:
152252
cleanedline = line.strip()
@@ -159,14 +259,33 @@ def getchangeentriesfromfile(outputfilename):
159259
comment = splittedlines[3].strip()
160260
date = splittedlines[4].strip()
161261
changeentries.append(ChangeEntry(revision, author, email, date, comment))
262+
162263
return changeentries
163264

265+
@staticmethod
266+
def getsimplehistoryfromfile(outputfilename):
267+
revisions = []
268+
if not os.path.isfile(outputfilename):
269+
shouter.shout("History file not found: " + outputfilename)
270+
shouter.shout("Skipping this part of history")
271+
return revisions
272+
273+
with open(outputfilename, 'r') as file:
274+
for line in file:
275+
revisions.append(line.strip())
276+
revisions.reverse() # to begin by the oldest
277+
return revisions
278+
164279
def getchangeentriesofbaseline(self, baselinetocompare):
165280
return self.getchangeentriesbytypeandvalue("baseline", baselinetocompare)
166281

167282
def getchangeentriesofstream(self, streamtocompare):
168283
shouter.shout("Start collecting changes since baseline creation")
169-
return self.getchangeentriesbytypeandvalue("stream", streamtocompare)
284+
missingchangeentries = {}
285+
changeentries = self.getchangeentriesbytypeandvalue("stream", streamtocompare)
286+
for changeentry in changeentries:
287+
missingchangeentries[changeentry.revision] = changeentry
288+
return missingchangeentries
170289

171290
def getchangeentriesbytypeandvalue(self, comparetype, value):
172291
dateformat = "yyyy-MM-dd HH:mm:ss"
@@ -176,6 +295,10 @@ def getchangeentriesbytypeandvalue(self, comparetype, value):
176295
shell.execute(comparecommand, outputfilename)
177296
return ImportHandler.getchangeentriesfromfile(outputfilename)
178297

298+
def gethistory(self, componentname, streamname):
299+
outputfilename = self.config.gethistorypath("History_%s_%s.txt" % (componentname, streamname))
300+
return ImportHandler.getsimplehistoryfromfile(outputfilename)
301+
179302

180303
class ChangeEntry:
181304
def __init__(self, revision, author, email, date, comment):
@@ -189,6 +312,9 @@ def getgitauthor(self):
189312
authorrepresentation = "%s <%s>" % (self.author, self.email)
190313
return shell.quote(authorrepresentation)
191314

315+
def tostring(self):
316+
return self.comment + " (Date: " + self.date + ", Author: " + self.author + ", Revision: " + self.revision + ")"
317+
192318

193319
class ComponentBaseLineEntry:
194320
def __init__(self, component, baseline, componentname, baselinename):

0 commit comments

Comments
 (0)