Skip to content

Commit 29a0a87

Browse files
committed
merge 'improveretryacceptmultiplechanges' (fixes #27 closes #18)
2 parents 30972db + d405c63 commit 29a0a87

3 files changed

Lines changed: 121 additions & 38 deletions

File tree

migration.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,19 @@ def migrate():
3838
initialize(config)
3939
streamuuid = config.streamuuid
4040
streamname = config.streamname
41+
branchname = streamname + "_branchpoint"
4142

4243
componentbaselineentries = rtc.getcomponentbaselineentriesfromstream(streamuuid)
4344
rtcworkspace.setnewflowtargets(streamuuid)
44-
git.branch(streamname)
45+
git.branch(branchname)
4546

4647
history = rtc.readhistory(componentbaselineentries, streamname)
4748
changeentries = rtc.getchangeentriesofstreamcomponents(componentbaselineentries)
4849

4950
rtc.acceptchangesintoworkspace(rtc.getchangeentriestoaccept(changeentries, history))
5051
shouter.shout("All changes until creation of stream '%s' accepted" % streamname)
51-
git.pushbranch(streamname)
52+
git.pushbranch(branchname)
53+
git.branch(streamname)
5254

5355
rtcworkspace.setcomponentstobaseline(componentbaselineentries, streamuuid)
5456
rtcworkspace.load()

rtcFunctions.py

Lines changed: 72 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -90,7 +90,8 @@ def accept(config, logpath, *changeentries):
9090
for changeEntry in changeentries:
9191
shouter.shout("Accepting: " + changeEntry.tostring())
9292
revisions = Changes._collectids(changeentries)
93-
Changes.latest_accept_command = config.scmcommand + " accept -v -o -r " + config.repo + " -t " + config.workspace + " --changes" + revisions
93+
Changes.latest_accept_command = config.scmcommand + " accept -v -o -r " + config.repo + " -t " + \
94+
config.workspace + " --changes" + revisions
9495
return shell.execute(Changes.latest_accept_command, logpath, "a")
9596

9697
@staticmethod
@@ -100,6 +101,21 @@ def _collectids(changeentries):
100101
ids += " " + changeentry.revision
101102
return ids
102103

104+
@staticmethod
105+
def arereleatedmergechangesets(changeentry1, changeentry2):
106+
if changeentry1 and changeentry2:
107+
if changeentry1.author == changeentry2.author or "merge" in changeentry2.comment.lower():
108+
return True
109+
return False
110+
111+
@staticmethod
112+
def tostring(*changes):
113+
logmessage = "Changes: \n"
114+
for change in changes:
115+
logmessage += change.tostring() + "\n"
116+
shouter.shout(logmessage)
117+
118+
103119

104120
class ImportHandler:
105121
def __init__(self, config):
@@ -168,51 +184,80 @@ def determineinitialbaseline(self, stream):
168184
return componentbaselinesentries
169185

170186
def acceptchangesintoworkspace(self, changeentries):
171-
git = Commiter
172187
amountofchanges = len(changeentries)
173188
shouter.shoutwithdate("Start accepting %s changesets" % amountofchanges)
174189
amountofacceptedchanges = 0
175-
skipnextchangeset = False
190+
changestoskip = 0
176191
reloaded = False
177192
for changeEntry in changeentries:
178193
amountofacceptedchanges += 1
179-
if skipnextchangeset:
180-
skipnextchangeset = False
194+
if changestoskip > 0:
195+
shouter.shout("Skipping " + changeEntry.tostring())
196+
changestoskip -= 1
181197
continue
182198
acceptedsuccesfully = Changes.accept(self.config, self.acceptlogpath,
183199
changeEntry) is 0
184200
if not acceptedsuccesfully:
185201
shouter.shout("Change wasnt succesfully accepted into workspace")
186-
skipnextchangeset = self.retryacceptincludingnextchangeset(changeEntry, changeentries)
202+
changestoskip = self.retryacceptincludingnextchangesets(changeEntry, changeentries)
187203
elif not reloaded:
188204
if self.is_reloading_necessary():
189205
WorkspaceHandler(self.config).load()
190206
reloaded = True
191207
shouter.shout("Accepted change %s/%s into working directory" % (amountofacceptedchanges, amountofchanges))
192-
git.addandcommit(changeEntry)
208+
Commiter.addandcommit(changeEntry)
193209

194210
@staticmethod
195211
def is_reloading_necessary():
196212
return shell.execute("git diff --exit-code") is 0
197213

198-
def retryacceptincludingnextchangeset(self, change, changes):
199-
successfull = False
200-
nextchangeentry = self.getnextchangeset(change, changes)
201-
if nextchangeentry and (change.author == nextchangeentry.author or "merge" in nextchangeentry.comment.lower()):
202-
shouter.shout("Next changeset: " + nextchangeentry.tostring())
203-
if (not self.config.useautomaticconflictresolution) and input("Press Enter to try to accept it with next changeset together, press any other key to skip this changeset and continue"):
204-
return False
205-
Changes.discard(self.config, change)
206-
successfull = Changes.accept(self.config, self.acceptlogpath, change, nextchangeentry) is 0
207-
if not successfull:
208-
Changes.discard(self.config, change, nextchangeentry)
209-
210-
if not successfull:
211-
shouter.shout("Last executed command: \n" + Changes.latest_accept_command)
212-
shouter.shout("Apropriate git commit command \n" + Commiter.getcommitcommand(change))
213-
if not input("Press Enter to continue or any other key to exit the program and rerun it with resume"):
214-
sys.exit("Please check the output and rerun programm with resume")
215-
return successfull
214+
@staticmethod
215+
def collect_changes_to_accept_to_avoid_conflicts(changewhichcantacceptedallone, changes):
216+
changestoaccept = [changewhichcantacceptedallone]
217+
nextchange = ImportHandler.getnextchangeset(changewhichcantacceptedallone, changes)
218+
219+
while True:
220+
if Changes.arereleatedmergechangesets(changewhichcantacceptedallone, nextchange):
221+
changestoaccept.append(nextchange)
222+
nextchange = ImportHandler.getnextchangeset(nextchange, changes)
223+
else:
224+
break
225+
return changestoaccept
226+
227+
def retryacceptincludingnextchangesets(self, change, changes):
228+
changestoskip = 0
229+
changestoaccept = ImportHandler.collect_changes_to_accept_to_avoid_conflicts(change, changes)
230+
amountofchangestoaccept = len(changestoaccept)
231+
232+
if amountofchangestoaccept > 1:
233+
Changes.tostring(*changestoaccept)
234+
if self.config.useautomaticconflictresolution or self.is_user_agreeing_to_accept_next_change(change):
235+
for index in range(1, amountofchangestoaccept):
236+
toaccept = changestoaccept[0:index + 1] # accept least possible amount of changes
237+
if Changes.accept(self.config, self.acceptlogpath, *toaccept) is 0:
238+
changestoskip = len(toaccept) - 1 # initialchange shouldnt be skipped
239+
break
240+
else:
241+
Changes.discard(self.config, *toaccept) # revert initial state
242+
return changestoskip
243+
244+
@staticmethod
245+
def is_user_agreeing_to_accept_next_change(change):
246+
messagetoask = "Press Y for accepting following changes, press N to skip"
247+
while True:
248+
answer = input(messagetoask).lower()
249+
if answer == "y":
250+
return True
251+
elif answer == "n":
252+
shouter.shout("Last executed command: \n" + Changes.latest_accept_command)
253+
shouter.shout("Apropriate git commit command \n" + Commiter.getcommitcommand(change))
254+
reallycontinue = "Do you want to continue? Y for continue, any key for abort"
255+
if input(reallycontinue).lower() == "y":
256+
return False
257+
else:
258+
sys.exit("Please check the output/log and rerun program with resume")
259+
else:
260+
shouter.shout("Please answer with Y/N, input was " + answer)
216261

217262
@staticmethod
218263
def getnextchangeset(currentchangeentry, changeentries):
@@ -228,6 +273,8 @@ def getchangeentriesofstreamcomponents(self, componentbaselineentries):
228273
shouter.shout("Start collecting changeentries")
229274
changeentriesbycomponentbaselineentry = {}
230275
for componentBaseLineEntry in componentbaselineentries:
276+
shouter.shout("Collect changes until baseline %s of component %s" %
277+
(componentBaseLineEntry.baselinename, componentBaseLineEntry.componentname))
231278
changeentries = self.getchangeentriesofbaseline(componentBaseLineEntry.baseline)
232279
for changeentry in changeentries:
233280
missingchangeentries[changeentry.revision] = changeentry

tests/test_rtcFunctions.py

Lines changed: 45 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,14 @@ class RtcFunctionsTestCase(unittest.TestCase):
1313
def setUp(self):
1414
self.workspace = "anyWorkspace"
1515
self.apath = "aLogPath"
16+
self.configBuilder = Builder()
1617

1718
@patch('rtcFunctions.shell')
1819
def test_Accept_AssertThatCorrectParamaterGetPassedToShell(self, shell_mock):
1920
revision1 = "anyRevision"
2021
revision2 = "anyOtherRevision"
2122
anyurl = "anyUrl"
22-
config = Builder().setrepourl(anyurl).setscmcommand("lscm").setworkspace(self.workspace).build()
23+
config = self.configBuilder.setrepourl(anyurl).setworkspace(self.workspace).build()
2324
Changes.accept(config, self.apath, self.createChangeEntry(revision1),
2425
self.createChangeEntry(revision2))
2526
expected_accept_command = "lscm accept -v -o -r %s -t %s --changes %s %s" % (anyurl, self.workspace, revision1,
@@ -33,13 +34,13 @@ def test_Discard_AssertThatCorrectParamaterGetPassedToShell(self, shell_mock):
3334
revision1 = "anyRevision"
3435
revision2 = "anyOtherRevision"
3536
anyurl = "anyUrl"
36-
config = Builder().setrepourl(anyurl).setscmcommand("lscm").setworkspace(self.workspace).build()
37+
config = self.configBuilder.setrepourl(anyurl).setworkspace(self.workspace).build()
3738
Changes.discard(config, self.createChangeEntry(revision1), self.createChangeEntry(revision2))
3839
expected_discard_command = "lscm discard -w %s -r %s -o %s %s" % (self.workspace, anyurl, revision1, revision2)
3940
shell_mock.execute.assert_called_once_with(expected_discard_command)
4041

41-
def createChangeEntry(self, revision):
42-
return ChangeEntry(revision, "anyAuthor", "anyEmail", "anyDate", "anyComment")
42+
def createChangeEntry(self, revision = "anyRevisionId", author = "anyAuthor", email = "anyEmail", comment = "anyComment", date = "anyDate"):
43+
return ChangeEntry(revision, author, email, date, comment)
4344

4445
def test_ReadChangesetInformationFromFile_WithoutLineBreakInComment_ShouldBeSuccessful(self):
4546
sample_file_path = self.get_Sample_File_Path("SampleCompareOutputWithoutLineBreaks.txt")
@@ -69,20 +70,53 @@ def test_ReadChangesetInformationFromFile_InUtf8_ShouldBeSuccesful(self):
6970
author = "John ÆØÅ"
7071
mail = "Jon.Doe@rtc2git.rocks"
7172
self.assert_Change_Entry(changeentries[0], author, mail, "Comment", "2015-05-26 10:40:00")
72-
73-
@patch('rtcFunctions.Changes')
73+
74+
@patch('rtcFunctions.shell')
7475
@patch('builtins.input', return_value='')
75-
def test_RetryAccept_AssertThatTwoChangesGetAcceptedTogether(self, inputmock, changesmock):
76+
def test_RetryAccept_AssertThatTwoChangesGetAcceptedTogether(self, inputmock, shellmock):
7677
changeentry1 = self.createChangeEntry("anyRevId")
7778
changeentry2 = self.createChangeEntry("anyOtherRevId")
7879
changeentries = [changeentry1, changeentry2]
79-
changesmock.accept.return_value = 0
8080

81-
config = Builder().setscmcommand("lscm").build()
81+
shellmock.execute.return_value = 0
82+
self.configBuilder.setrepourl("anyurl").setuseautomaticconflictresolution("True").setworkspace("anyWs")
83+
config = self.configBuilder.build()
84+
8285
handler = ImportHandler(config)
83-
handler.retryacceptincludingnextchangeset(changeentry1, changeentries)
86+
handler.retryacceptincludingnextchangesets(changeentry1, changeentries)
87+
88+
expectedshellcommand = 'lscm accept -v -o -r anyurl -t anyWs --changes anyRevId anyOtherRevId'
89+
shellmock.execute.assert_called_once_with(expectedshellcommand, handler.config.getlogpath("accept.txt"), "a")
90+
91+
def test_collectChangeSetsToAcceptToAvoidMergeConflict_ShouldCollectThreeChangesets(self):
92+
mychange1 = self.createChangeEntry("doSomethingOnOldRev")
93+
mychange2 = self.createChangeEntry("doSomethingElseOnOldRev")
94+
mymergechange = self.createChangeEntry("anyRev", comment="merge change")
95+
changefromsomeoneelse = self.createChangeEntry(author="anyOtherAuthor", revision="2", comment="anotherCommit")
96+
97+
changeentries = [mychange1, mychange2, mymergechange, changefromsomeoneelse]
98+
99+
handler = ImportHandler(self.configBuilder.build())
100+
collectedchanges = handler.collect_changes_to_accept_to_avoid_conflicts(mychange1, changeentries)
101+
self.assertTrue(mychange1 in collectedchanges)
102+
self.assertTrue(mychange2 in collectedchanges)
103+
self.assertTrue(mymergechange in collectedchanges)
104+
self.assertFalse(changefromsomeoneelse in collectedchanges)
105+
self.assertEqual(3, len(collectedchanges))
106+
107+
@patch('builtins.input', return_value='Y')
108+
def test_useragreeing_answeris_y_expecttrue(self, inputmock):
109+
handler = ImportHandler(self.configBuilder.build())
110+
self.assertTrue(handler.is_user_agreeing_to_accept_next_change(self.createChangeEntry()))
84111

85-
changesmock.accept.assert_called_with(config, handler.acceptlogpath, changeentry1, changeentry2)
112+
@patch('builtins.input', return_value='n')
113+
def test_useragreeing_answeris_n_expectfalseandexception(self, inputmock):
114+
handler = ImportHandler(self.configBuilder.build())
115+
try:
116+
handler.is_user_agreeing_to_accept_next_change(self.createChangeEntry())
117+
self.fail("Should have exit the program")
118+
except SystemExit as e:
119+
self.assertEqual("Please check the output/log and rerun program with resume", e.code)
86120

87121
def get_Sample_File_Path(self, filename):
88122
testpath = os.path.realpath(__file__)

0 commit comments

Comments
 (0)