Diff
Modified: trunk/Tools/Scripts/hooks/prepare-commit-msg (295387 => 295388)
--- trunk/Tools/Scripts/hooks/prepare-commit-msg 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/hooks/prepare-commit-msg 2022-06-08 18:13:57 UTC (rev 295388)
@@ -67,7 +67,7 @@
'''.format(
title=os.environ.get('COMMIT_MESSAGE_TITLE', '') or 'Need a short description (OOPS!).',
bugs=os.environ.get('COMMIT_MESSAGE_BUG', '') or 'Need the bug URL (OOPS!).',
- content='\n'.join(commit_message),
+ content='\n'.join(commit_message) + os.environ.get('COMMIT_MESSAGE_CONTENT', ''),
)
except subprocess.CalledProcessError:
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/setup.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/setup.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -29,7 +29,7 @@
setup(
name='webkitscmpy',
- version='4.15.4',
+ version='5.0.0',
description='Library designed to interact with git and svn repositories.',
long_description=readme(),
classifiers=[
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/__init__.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -46,7 +46,7 @@
"Please install webkitcorepy with `pip install webkitcorepy --extra-index-url <package index URL>`"
)
-version = Version(4, 15, 4)
+version = Version(5, 0, 0)
AutoInstall.register(Package('fasteners', Version(0, 15, 0)))
AutoInstall.register(Package('jinja2', Version(2, 11, 3)))
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/git-repo.json (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/git-repo.json 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/git-repo.json 2022-06-08 18:13:57 UTC (rev 295388)
@@ -118,6 +118,20 @@
],
"branch-a": [
{
+ "hash": "fff83bb2d9171b4d9196e977eb0508fd57e7a08d",
+ "author": {
+ "name": "Jonathan Bedard",
+ "emails": [
+ "jbed...@apple.com"
+ ]
+ },
+ "timestamp": 1601661000,
+ "branch": "main",
+ "message": "2nd commit\n",
+ "identifier": "2@main",
+ "revision": 2
+ },
+ {
"hash": "a30ce8494bf1ac2807a69844f726be4a9843ca55",
"author": {
"name": "Jonathan Bedard",
@@ -145,5 +159,67 @@
"identifier": "2.2@branch-a",
"revision": 6
}
+ ],
+ "eng/squash-branch": [
+ {
+ "hash": "d8bce26fa65c6fc8f39c17927abb77f69fab82fc",
+ "author": {
+ "name": "Jonathan Bedard",
+ "emails": [
+ "jbed...@apple.com"
+ ]
+ },
+ "timestamp": 1601668000,
+ "branch": "main",
+ "message": "Patch Series\n",
+ "identifier": "5@main",
+ "revision": 9
+ },
+ {
+ "hash": "d631ca664d338ef8aa153a6e4088ab5f633fd43b",
+ "author": {
+ "name": "Zhifei Fang",
+ "emails": [
+ "zhifei_f...@apple.com"
+ ]
+ },
+ "timestamp": 1601672000,
+ "branch": "eng/squash-branch",
+ "message": "Changed something 1",
+ "changeFiles": {
+ "a.cpp": "diff --git a/a.cpp b/a.cpp"
+ }
+ },
+ {
+ "hash": "651a4775399150c14d420ae29160a7111ff49306",
+ "author": {
+ "name": "Zhifei Fang",
+ "emails": [
+ "zhifei_f...@apple.com"
+ ]
+ },
+ "timestamp": 1601674000,
+ "branch": "eng/squash-branch",
+ "message": "Changed something 2",
+ "changeFiles": {
+ "c.cpp": "diff --git a/c.cpp b/c.cpp"
+ }
+ },
+ {
+ "hash": "f8a78d2db925929b75b17396ec1d7296078d49b6",
+ "author": {
+ "name": "Zhifei Fang",
+ "emails": [
+ "zhifei_f...@apple.com"
+ ]
+ },
+ "timestamp": 1601677000,
+ "branch": "eng/squash-branch",
+ "message": "Changed something 3",
+ "changeFiles": {
+ "a.cpp": "diff --git a/a.cpp b/a.cpp",
+ "b.cpp": "diff --git a/b.cpp b/b.cpp"
+ }
+ }
]
}
\ No newline at end of file
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/git.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/git.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/local/git.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -67,16 +67,26 @@
with open(datafile or os.path.join(os.path.dirname(os.path.dirname(__file__)), 'git-repo.json')) as file:
self.commits = json.load(file)
for key, commits in self.commits.items():
- self.commits[key] = [Commit(**kwargs) for kwargs in commits]
+ commit_objs = []
+ for kwargs in commits:
+ changeFiles = None
+ if 'changeFiles' in kwargs:
+ changeFiles = kwargs['changeFiles']
+ del kwargs['changeFiles']
+ commit = Commit(**kwargs)
+ if changeFiles:
+ setattr(commit, '__mock__changeFiles', changeFiles)
+ commit_objs.append(commit)
+ self.commits[key] = commit_objs
if not git_svn:
for commit in self.commits[key]:
commit.revision = None
self.head = self.commits[self.default_branch][-1]
- self.remotes = {'origin/{}'.format(branch): commits[-1] for branch, commits in self.commits.items()}
+ self.remotes = {'origin/{}'.format(branch): commits[:] for branch, commits in self.commits.items()}
for name in (remotes or {}).keys():
for branch, commits in self.commits.items():
- self.remotes['{}/{}'.format(name, branch)] = commits[-1]
+ self.remotes['{}/{}'.format(name, branch)] = commits[:]
self.tags = {}
@@ -303,7 +313,7 @@
),
) if self.find(args[2]) else mocks.ProcessCompletion(returncode=128),
), mocks.Subprocess.Route(
- self.executable, 'log', '--format=fuller', '--no-decorate', '--date=unix', re.compile(r'.+\.\.\..+'),
+ self.executable, 'log', '--format=fuller', '--no-decorate', '--date=unix', re.compile(r'.+'),
cwd=self.path,
generator=lambda *args, **kwargs: mocks.ProcessCompletion(
returncode=0,
@@ -318,15 +328,16 @@
author=commit.author.name,
email=commit.author.email,
date=commit.timestamp,
- log='\n'.join([
- (' ' + line) if line else '' for line in commit.message.splitlines()
- ] + ([
- ' git-svn-id: https://svn.{}/repository/{}/trunk@{} 268f45cc-cd09-0410-ab3c-d52691b4dbfc'.format(
+ log='\n'.join(
+ [
+ (' ' + line) if line else '' for line in commit.message.splitlines()
+ ] + ([' git-svn-id: https://svn.{}/repository/{}/trunk@{} 268f45cc-cd09-0410-ab3c-d52691b4dbfc'.format(
self.remote.split('@')[-1].split(':')[0],
os.path.basename(path),
- commit.revision,
- )] if git_svn else []),
- )) for commit in list(self.commits_in_range(args[5].split('...')[-1], args[5].split('...')[0]))[:-1]
+ commit.revision,
+ )] if git_svn else []),
+ )
+ ) for commit in list(self.rev_list(args[5]))
])
)
), mocks.Subprocess.Route(
@@ -343,15 +354,16 @@
author=commit.author.name,
email=commit.author.email,
date=commit.timestamp if '--date=unix' in args else datetime.utcfromtimestamp(commit.timestamp + time.timezone).strftime('%a %b %d %H:%M:%S %Y +0000'),
- log='\n'.join([
- (' ' + line) if line else '' for line in commit.message.splitlines()
- ] + ([
- ' git-svn-id: https://svn.{}/repository/{}/trunk@{} 268f45cc-cd09-0410-ab3c-d52691b4dbfc'.format(
+ log='\n'.join(
+ [
+ (' ' + line) if line else '' for line in commit.message.splitlines()
+ ] + ([' git-svn-id: https://svn.{}/repository/{}/trunk@{} 268f45cc-cd09-0410-ab3c-d52691b4dbfc'.format(
self.remote.split('@')[-1].split(':')[0],
os.path.basename(path),
- commit.revision,
- )] if git_svn else []),
- )) for commit in self.commits_in_range(self.commits[self.default_branch][0].hash, args[2])
+ commit.revision,
+ )] if git_svn else [])
+ )
+ ) for commit in self.rev_list(args[2])
])
)
), mocks.Subprocess.Route(
@@ -362,6 +374,13 @@
stdout='{}\n'.format(self.count(args[4]))
) if self.find(args[4]) else mocks.ProcessCompletion(returncode=128),
), mocks.Subprocess.Route(
+ self.executable, 'rev-list', re.compile(r'.+'),
+ cwd=self.path,
+ generator=lambda *args, **kwargs: mocks.ProcessCompletion(
+ returncode=0,
+ stdout='\n'.join(map(lambda commit: commit.hash, self.rev_list(args[2])))
+ ),
+ ), mocks.Subprocess.Route(
self.executable, 'show', '-s', '--format=%ct', re.compile(r'.+'),
cwd=self.path,
generator=lambda *args, **kwargs: mocks.ProcessCompletion(
@@ -548,10 +567,7 @@
stdout='\n'.join([
'--- a/ChangeLog\n+++ b/ChangeLog\n@@ -1,0 +1,0 @@\n{}'.format(
'\n'.join(['+ {}'.format(line) for line in commit.message.splitlines()])
- ) for commit in list(self.commits_in_range(
- args[2].split('..')[0],
- args[2].split('..')[-1] if '..' in args[2] else self.commits[self.default_branch][-1].hash,
- ))[:-1]
+ ) for commit in list(self.rev_list(args[2] if '..' in args[2] else '{}..HEAD'.format(args[2])))
])
)
), mocks.Subprocess.Route(
@@ -558,7 +574,11 @@
self.executable, 'reset', 'HEAD',
cwd=self.path,
generator=lambda *args, **kwargs: self.reset(int(args[2].split('~')[-1]) if '~' in args[2] else None),
- ), mocks.Subprocess.Route(
+ ), mocks.Subprocess.Route(
+ self.executable, 'reset', re.compile(r'.+'),
+ cwd=self.path,
+ generator=lambda *args, **kwargs: self.reset_commit(args[2]),
+ ), mocks.Subprocess.Route(
self.executable,
cwd=self.path,
completion=mocks.ProcessCompletion(
@@ -617,12 +637,16 @@
if len(split) == 2 and Commit.NUMBER_RE.match(split[1]):
found = self.find(split[0])
difference = int(split[1])
- if difference < found.identifier:
- difference += self.commits[found.branch][0].identifier
- return self.commits[found.branch][found.identifier - difference]
- difference -= found.identifier
- if found.branch_point and difference < found.branch_point:
- return self.commits[self.default_branch][found.branch_point - difference - 1]
+ if split[0] in self.remotes:
+ all_commits = self.resolve_all_commits(found.branch, remote=split[0].replace('/{}'.format(found.branch), ''))
+ else:
+ all_commits = self.resolve_all_commits(found.branch)
+ head_index = None
+ for i in range(len(all_commits)):
+ if found == all_commits[i]:
+ head_index = i
+ if head_index is not None and head_index - difference >= 0:
+ return all_commits[head_index - difference]
return None
something = str(something).replace('remotes/', '')
@@ -639,7 +663,7 @@
if something in self.tags.keys():
return self.tags[something]
if something in self.remotes.keys():
- return self.remotes[something]
+ return self.remotes[something][-1]
for branch, commits in self.commits.items():
if branch == something:
@@ -652,17 +676,9 @@
return None
def count(self, something):
- if '..' not in something:
- match = self.find(something)
- return (match.branch_point or 0) + match.identifier
+ rev_list = self.rev_list(something)
+ return len(rev_list)
- a, b = something.split('..')
- a = self.find(a)
- b = self.find(b)
- if a.branch_point == b.branch_point:
- return abs(b.identifier - a.identifier)
- return b.identifier
-
def branches_on(self, hash):
result = set()
found_identifier = 0
@@ -670,8 +686,9 @@
for commit in commits:
if commit.hash.startswith(hash):
- found_identifier = max(commit.identifier, found_identifier)
- result.add(branch)
+ if commit.identifier is not None:
+ found_identifier = max(commit.identifier, found_identifier)
+ result.add(commit.branch)
if self.default_branch in result:
for branch, commits in self.commits.items():
@@ -691,15 +708,11 @@
self.detached = something not in self.commits.keys()
return True
return False
- if self.head.branch == self.default_branch:
- self.commits[something] = [self.head]
- else:
- self.commits[something] = [
- commit for commit in self.commits[self.head.branch]
- if not commit.branch_point or commit.identifier <= self.head.identifier
- ]
- self.commits[something][-1] = Commit.from_json(Commit.Encoder().default(self.head))
+ self.commits[something] = [Commit.from_json(Commit.Encoder().default(self.head))]
+ # Copy one more to create a bridge commit
+ self.commits[something].append(Commit.from_json(Commit.Encoder().default(self.head)))
self.head = self.commits[something][-1]
+ setattr(self.head, 'bridge_commit', True)
self.head.branch = something
if not self.head.branch_point:
self.head.branch_point = self.head.identifier
@@ -808,52 +821,6 @@
stdout=stdout.getvalue(),
)
- def commits_in_range(self, begin, end):
- begin = begin.replace('remotes/', '') if begin else begin
- end = end.replace('remotes/', '') if end else end
-
- if begin and self.remotes.get(begin):
- begin = self.remotes.get(begin).hash
- if end and self.remotes.get(end):
- end = self.remotes.get(end).hash
-
- branches = [self.default_branch]
- if end in self.commits.keys() and end != self.default_branch:
- branches.insert(0, end)
- else:
- for branch, commits in self.commits.items():
- if branch == self.default_branch:
- continue
- for commit in commits:
- if commit.hash.startswith(end):
- branches.insert(0, branch)
- break
- if len(branches) > 1:
- break
-
- in_range = False
- previous = None
- for branch in branches:
- for commit in reversed(self.commits[branch]):
- if branch == begin:
- break
- if commit.hash.startswith(end) or end == branch:
- in_range = True
- if in_range and (not previous or commit.hash != previous.hash):
- yield commit
- previous = commit
- if begin and commit.hash.startswith(begin):
- in_range = False
- break
-
- in_range = False
- if not previous or branch == self.default_branch:
- continue
-
- for commit in reversed(self.commits[self.default_branch]):
- if previous.branch_point == commit.identifier:
- end = commit.hash
-
@decorators.hybridmethod
def config(context, path=None):
if isinstance(context, type):
@@ -917,6 +884,9 @@
return mocks.ProcessCompletion(returncode=1, stdout='no changes added to commit (use "git add" and/or "git commit -a")\n')
if not amend:
+ # Remove the temp bridge commit
+ if hasattr(self.head, 'bridge_commit'):
+ self.commits[self.head.branch].remove(self.head)
self.head = Commit(
branch=self.branch, repository_id=self.head.repository_id,
timestamp=int(time.time()),
@@ -926,10 +896,11 @@
self.commits[self.branch].append(self.head)
self.head.author = Contributor(self.config()['user.name'], [self.config()['user.email']])
- self.head.message = '{}{}\nReviewed by Jonathan Bedard\n\n * {}\n'.format(
+ self.head.message = '{}{}\nReviewed by Jonathan Bedard\n\n * {}\n{}'.format(
env.get('COMMIT_MESSAGE_TITLE', '') or '[Testing] {} commits'.format('Amending' if amend else 'Creating'),
('\n' + env.get('COMMIT_MESSAGE_BUG', '')) if env.get('COMMIT_MESSAGE_BUG', '') else '',
'\n * '.join(self.staged.keys()),
+ env.get('COMMIT_MESSAGE_CONTENT', '')
)
self.head.hash = hashlib.sha256(string_utils.encode(self.head.message)).hexdigest()[:40]
self.staged = {}
@@ -1003,16 +974,19 @@
return mocks.ProcessCompletion(returncode=128, stdout="fatal: pathspec '{}' did not match any files\n".format(file))
for key, value in self.modified.items():
self.staged[key] = value
- self.modified = {}
+ del self.modified[file]
return mocks.ProcessCompletion(returncode=0)
def rebase(self, target, base, head):
if target not in self.commits or base not in self.commits or head not in self.commits:
return mocks.ProcessCompletion(returncode=1)
- for commit in self.commits[head]:
- commit.branch_point = self.commits[target][-1].branch_point or self.commits[target][-1].identifier
- if self.commits[target][-1].branch_point:
- commit.identifier += self.commits[target][-1].identifier
+
+ base = self.commits[target][-1]
+ self.commits[head][0] = base
+ for commit in self.commits[head][1:]:
+ commit.branch_point = base.branch_point or base.identifier
+ if base.branch_point:
+ commit.identifier += base.identifier
return mocks.ProcessCompletion(returncode=0)
def pull(self, autostash=False):
@@ -1052,17 +1026,43 @@
)
def push(self, remote, branch):
- self.remotes['{}/{}'.format(remote, branch)] = self.commits[branch][-1]
+ self.remotes['{}/{}'.format(remote, branch)] = self.commits[branch][:]
return mocks.ProcessCompletion(returncode=0)
def dcommit(self, remote='origin', branch=None):
branch = branch or self.default_branch
- self.remotes['{}/{}'.format(remote, branch)] = self.commits[branch][-1]
+ self.remotes['{}/{}'.format(remote, branch)] = self.commits[branch][:]
return mocks.ProcessCompletion(
returncode=0,
stdout='Committed r{}\n\tM\tFiles/Changed.txt\n'.format(self.commits[branch][-1].revision),
)
+ def reset_commit(self, something):
+ commit = self.find(something)
+ pre_branch = self.branch
+ rev_list = self.rev_list('HEAD...{}'.format(something))
+ commits = self.commits[self.branch]
+ if commit is not None:
+ self.head = commit
+ for commit in rev_list:
+ if hasattr(commit, '__mock__changeFiles'):
+ files = getattr(commit, '__mock__changeFiles')
+ for file in files:
+ self.modified[file] = files[file]
+ commits.remove(commit)
+ if pre_branch != self.branch:
+ # Add a fake commit to simulate a same commit in different branch
+ bridge_commit = Commit(
+ hash=self.head.hash, revision=self.head.revision,
+ identifier=self.head.identifier, branch=pre_branch, branch_point=self.head.branch_point,
+ timestamp=self.head.timestamp, author=self.head.author, message=self.head.message,
+ order=self.head.order, repository_id=self.head.repository_id
+ )
+ setattr(bridge_commit, 'bridge_commit', True)
+ commits.append(bridge_commit)
+ self.head = commits[-1]
+ return mocks.ProcessCompletion(returncode=0)
+
def reset(self, index):
if index is None:
self.modified = {}
@@ -1072,6 +1072,100 @@
self.head = self.commits[self.head.branch][-(index + 1)]
return mocks.ProcessCompletion(returncode=0)
+ def resolve_all_commits(self, branch, remote=None):
+ if not remote:
+ all_commits = self.commits[branch][:]
+ else:
+ all_commits = self.remotes['{}/{}'.format(remote, branch)][:]
+ last_commit = all_commits[0]
+ while last_commit.branch != branch:
+ head_index = None
+ if not remote:
+ commits_part = self.commits[last_commit.branch]
+ else:
+ commits_part = self.remotes['{}/{}'.format(remote, last_commit.branch)]
+ for i in range(len(commits_part)):
+ if commits_part[i].hash == last_commit.hash:
+ head_index = i
+ break
+ all_commits = commits_part[:head_index] + all_commits
+ last_commit = all_commits[0]
+ if last_commit.branch == self.default_branch and last_commit.identifier == 1:
+ break
+ if remote:
+ for commit in all_commits:
+ setattr(commit, '__mock__remotes', set([remote]))
+ return all_commits
+
+ def rev_list(self, something):
+ """
+ A..B = A u B - A
+ A...B = A u B - A n B
+ """
+ two_dots = False
+ triple_dots = False
+ a_commit = None
+ a_remote = None
+ b_commit = None
+ b_remote = None
+ if '...' in something:
+ something = something.split('...')
+ triple_dots = True
+ a_commit = self.find(something[0])
+ b_commit = self.find(something[1])
+ if something[0] in self.remotes:
+ a_remote = something[0].replace('/{}'.format(a_commit.branch), '')
+ if something[1] in self.remotes:
+ b_remote = something[1].replace('/{}'.format(b_commit.branch), '')
+ elif '..' in something:
+ something = something.split('..')
+ two_dots = True
+ a_commit = self.find(something[0])
+ b_commit = self.find(something[1])
+ if something[0] in self.remotes:
+ a_remote = something[0].replace('/{}'.format(a_commit.branch), '')
+ if something[1] in self.remotes:
+ b_remote = something[1].replace('/{}'.format(b_commit.branch), '')
+ else:
+ a_commit = self.find(something)
+ if something in self.remotes:
+ a_remote = something.replace('/{}'.format(a_commit.branch), '')
+
+ a_commits = []
+ a_branch_commits = self.resolve_all_commits(a_commit.branch, remote=a_remote) if a_commit else []
+ for commit in a_branch_commits:
+ a_commits.append(commit)
+ if commit.hash == a_commit.hash:
+ break
+
+ b_commits = []
+ b_branch_commits = self.resolve_all_commits(b_commit.branch, remote=b_remote) if b_commit else []
+ for commit in b_branch_commits:
+ b_commits.append(commit)
+ if commit.hash == b_commit.hash:
+ break
+
+ if not two_dots and not triple_dots:
+ return list(reversed(a_commits))
+
+ res = []
+ # To make things easier, we only mock that two branch will share same init commit
+ assert a_commits[0].hash == b_commits[0].hash
+ for i in range(max(len(a_commits), len(b_commits))):
+ if i >= len(a_commits) and i < len(b_commits):
+ res.append(b_commits[i])
+ elif i >= len(b_commits) and i < len(a_commits):
+ if triple_dots:
+ res.append(a_commits[i])
+ elif i < len(b_commits) and i < len(a_commits) and a_commits[i].hash != b_commits[i].hash:
+ if triple_dots:
+ res.append(a_commits[i])
+ res.append(b_commits[i])
+ if two_dots:
+ res.append(b_commits[i])
+ res.reverse()
+ return res
+
def _install_git_lfs(self):
self.has_git_lfs = True
return mocks.ProcessCompletion(
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/bitbucket.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -47,7 +47,17 @@
with open(datafile or os.path.join(os.path.dirname(os.path.dirname(__file__)), 'git-repo.json')) as file:
self.commits = json.load(file)
for key, commits in self.commits.items():
- self.commits[key] = [Commit(**kwargs) for kwargs in commits]
+ commit_objs = []
+ for kwargs in commits:
+ changeFiles = None
+ if 'changeFiles' in kwargs:
+ changeFiles = kwargs['changeFiles']
+ del kwargs['changeFiles']
+ commit = Commit(**kwargs)
+ if changeFiles:
+ setattr(commit, '__mock__changeFiles', changeFiles)
+ commit_objs.append(commit)
+ self.commits[key] = commit_objs
if not git_svn:
for commit in self.commits[key]:
commit.revision = None
@@ -56,6 +66,22 @@
self.tags = {}
self.pull_requests = []
+ def resolve_all_commits(self, branch):
+ all_commits = self.commits[branch][:]
+ last_commit = all_commits[0]
+ while last_commit.branch != branch:
+ head_index = None
+ commits_part = self.commits[last_commit.branch]
+ for i in range(len(commits_part)):
+ if commits_part[i].hash == last_commit.hash:
+ head_index = i
+ break
+ all_commits = commits_part[:head_index] + all_commits
+ last_commit = all_commits[0]
+ if last_commit.branch == self.default_branch and last_commit.identifier == 1:
+ break
+ return all_commits
+
def commit(self, ref):
if ref in self.commits:
return self.commits[ref][-1]
@@ -75,11 +101,14 @@
return None
delta = int(delta)
- if delta < commit.identifier:
- return self.commits[commit.branch][commit.identifier - delta - 1]
- delta -= commit.identifier
- if commit.branch_point and delta < commit.branch_point:
- return self.commits[self.default_branch][commit.branch_point - delta - 1]
+ all_commits = self.resolve_all_commits(commit.branch)
+ commit_index = 0
+ for i in range(len(all_commits)):
+ if all_commits[i].hash == commit.hash:
+ commit_index = i
+ break
+ if commit_index - delta >= 0:
+ return all_commits[commit_index - delta]
return None
def _branches_default(self, url):
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/mocks/remote/git_hub.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -49,7 +49,17 @@
with open(datafile or os.path.join(os.path.dirname(os.path.dirname(__file__)), 'git-repo.json')) as file:
self.commits = jsonlib.load(file)
for key, commits in self.commits.items():
- self.commits[key] = [Commit(**kwargs) for kwargs in commits]
+ commit_objs = []
+ for kwargs in commits:
+ changeFiles = None
+ if 'changeFiles' in kwargs:
+ changeFiles = kwargs['changeFiles']
+ del kwargs['changeFiles']
+ commit = Commit(**kwargs)
+ if changeFiles:
+ setattr(commit, '__mock__changeFiles', changeFiles)
+ commit_objs.append(commit)
+ self.commits[key] = commit_objs
if not git_svn:
for commit in self.commits[key]:
commit.revision = None
@@ -59,6 +69,22 @@
self.pull_requests = []
self.releases = releases or dict()
+ def resolve_all_commits(self, branch):
+ all_commits = self.commits[branch][:]
+ last_commit = all_commits[0]
+ while last_commit.branch != branch:
+ head_index = None
+ commits_part = self.commits[last_commit.branch]
+ for i in range(len(commits_part)):
+ if commits_part[i].hash == last_commit.hash:
+ head_index = i
+ break
+ all_commits = commits_part[:head_index] + all_commits
+ last_commit = all_commits[0]
+ if last_commit.branch == self.default_branch and last_commit.identifier == 1:
+ break
+ return all_commits
+
def commit(self, ref):
if ref in self.commits:
return self.commits[ref][-1]
@@ -78,11 +104,15 @@
return None
delta = int(delta)
- if delta < commit.identifier:
- return self.commits[commit.branch][commit.identifier - delta - 1]
- delta -= commit.identifier
- if commit.branch_point and delta < commit.branch_point:
- return self.commits[self.default_branch][commit.branch_point - delta - 1]
+ all_commits = self.resolve_all_commits(commit.branch)
+ commit_index = 0
+ for i in range(len(all_commits)):
+ if all_commits[i].hash == commit.hash:
+ commit_index = i
+ break
+
+ if commit_index - delta >= 0:
+ return all_commits[commit_index - delta]
return None
def _api_response(self, url):
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/__init__.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -31,6 +31,7 @@
from .clean import Clean, DeletePRBranches
from .command import Command
from .commit import Commit
+from .squash import Squash
from .checkout import Checkout
from .credentials import Credentials
from .find import Find, Info
@@ -79,7 +80,7 @@
Blame, Branch, Canonicalize, Checkout,
Clean, Find, Info, Land, Log, Pull,
PullRequest, Revert, Setup, InstallGitLFS,
- Credentials, Commit, DeletePRBranches,
+ Credentials, Commit, DeletePRBranches, Squash
]
if subversion:
programs.append(SetupGitSvn)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/pull_request.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -26,6 +26,7 @@
from .command import Command
from .branch import Branch
+from .squash import Squash
from webkitbugspy import Tracker
from webkitcorepy import arguments, run, Terminal
@@ -41,6 +42,7 @@
@classmethod
def parser(cls, parser, loggers=None):
Branch.parser(parser, loggers=loggers)
+ Squash.parser(parser, loggers=loggers)
parser.add_argument(
'--add', '--no-add',
dest='will_add', default=None,
@@ -54,6 +56,12 @@
action=""
)
parser.add_argument(
+ '--squash',
+ dest='squash', default=False,
+ action='',
+ help='Combine all commits on the current development branch into a single commit before pushing',
+ )
+ parser.add_argument(
'--defaults', '--no-defaults', action="" default=None,
help='Do not prompt the user for defaults, always use (or do not use) them',
)
@@ -431,8 +439,11 @@
if not branch_point:
return 1
- result = cls.create_commit(args, repository, **kwargs)
- if result:
- return result
+ if args.squash:
+ result = Squash.squash_commit(args, repository, branch_point, **kwargs)
+ else:
+ result = cls.create_commit(args, repository, **kwargs)
+ if result:
+ return result
return cls.create_pull_request(repository, args, branch_point)
Added: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/squash.py (0 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/squash.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/program/squash.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -0,0 +1,145 @@
+# Copyright (C) 2022 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS "AS IS" AND
+# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR
+# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import logging
+import re
+import sys
+import os
+import re
+from unittest import result
+
+from .command import Command
+from .branch import Branch
+
+from webkitcorepy import arguments, run, Terminal
+from webkitscmpy import local, log, remote
+from ..commit import Commit
+
+
+class Squash(Command):
+ name = 'squash'
+ help = 'Combine all commits on the current development branch into a single commit'
+
+ @classmethod
+ def parser(cls, parser, loggers=None):
+ group = parser.add_mutually_exclusive_group(required=False)
+ group.add_argument(
+ '--interactive',
+ dest='interactive',
+ default=False,
+ action='',
+ help='Use git rebase to interactivly to select what commits you want to squash.'
+ )
+
+ group.add_argument(
+ '--new-msg', '--new-message', '--no-sub-commit-message',
+ default=False,
+ dest='no_sub_commit_message',
+ action='',
+ help='Do not include messages of commits you want squash.'
+ )
+
+ parser.add_argument(
+ '--base-commit',
+ dest='base_commit',
+ help='git hash, svn revision or identifer for the base commit that you want to squash to (merged commit will not include this commit)',
+ default=None,
+ )
+
+ @classmethod
+ def get_commits_hashes(cls, repository, base_commit):
+ result = run([repository.executable(), 'rev-list', 'HEAD...{}'.format(base_commit.hash)], capture_output=True, cwd=repository.root_path)
+ if result.returncode:
+ sys.stderr.write(result.stderr)
+ sys.stderr.write('Failed to get all commits from HEAD to {}'.format(base_commit.hash))
+ return None
+ return result.stdout.decode('utf-8').strip().splitlines()
+
+ @classmethod
+ def undo_reset(cls, repository):
+ return run([repository.executable(), 'reset', "'HEAD@{1}'"], cwd=repository.root_path)
+
+ @classmethod
+ def squash_commit(cls, args, repository, branch_point, **kwargs):
+ # Make sure we have the commit that user want to revert
+ try:
+ if args.base_commit:
+ base_commit = repository.find(args.base_commit, include_log=True)
+ if hasattr(base_commit, 'identifier') and base_commit <= branch_point:
+ sys.stderr.write('It seems you are trying to sqaush beyond branch point.')
+ return 1
+ else:
+ base_commit = branch_point
+ # Check if there are any outstanding changes:
+ if repository.modified():
+ sys.stderr.write('Please commit your changes or stash them before you squash to base commit: {}'.format(base_commit))
+ return 1
+ except (local.Scm.Exception, ValueError) as exception:
+ # ValueErrors and Scm exceptions usually contain enough information to be displayed
+ # to the user as an error
+ sys.stderr.write('Could not find "{}"'.format(args.base_commit) + '\n')
+ return 1
+
+ if args.interactive:
+ result = run([repository.executable(), 'rebase', '-i'] + [base_commit.hash], cwd=repository.root_path)
+ else:
+ previous_history = ''
+ commit_hash_list = cls.get_commits_hashes(repository, base_commit)
+ if not commit_hash_list:
+ return 1
+ if not args.no_sub_commit_message:
+ commits = map(lambda hash: repository.find(hash, include_log=True), commit_hash_list)
+ previous_history += '\n\n'.join(map(lambda commit: commit.message, commits))
+ result = run([repository.executable(), 'reset'] + [base_commit.hash], cwd=repository.root_path)
+ if result.returncode:
+ sys.stderr.write('Failed to merge the diff.')
+ cls.undo_reset()
+ modified_files = repository.modified()
+ if not modified_files:
+ sys.stderr.write('Failed to detect any diff to merge.')
+ return 1
+ for file in modified_files:
+ if run([repository.executable(), 'add', file], cwd=repository.root_path).returncode:
+ sys.stderr.write("Failed to add '{}'\n".format(file))
+ return 1
+ env = os.environ
+ env['COMMIT_MESSAGE_CONTENT'] = '\n\nThis commit include:\n\n' + previous_history
+
+ result = run([repository.executable(), 'commit', '--date=now'], cwd=repository.root_path, env=env)
+ log.info(' Squashed {} commits'.format(len(commit_hash_list)))
+
+ if result.returncode:
+ sys.stderr.write('Failed to generate squashed commit\n')
+ return 1
+ return 0
+
+ @classmethod
+ def main(cls, args, repository, **kwargs):
+ if not isinstance(repository, local.Git):
+ sys.stderr.write("Can only '{}' on a native Git repository\n".format(cls.name))
+ return 1
+
+ branch_point = Branch.branch_point(repository)
+ if not branch_point:
+ return 1
+
+ return cls.squash_commit(args, repository, branch_point, **kwargs)
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/checkout_unittest.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/checkout_unittest.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/checkout_unittest.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -129,13 +129,16 @@
reviews=[dict(user=dict(login='rreviewer'), state='CHANGES_REQUESTED')],
draft=False,
)]
- repo.commits['eng/example'] = [Commit(
- hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
- identifier='3.1@eng/example',
- timestamp=int(time.time()) - 60,
- author=Contributor('Tim Committer', ['tcommit...@webkit.org']),
- message='To Be Committed\n\nReviewed by NOBODY (OOPS!).\n',
- )]
+ repo.commits['eng/example'] = [
+ repo.commits[repo.default_branch][2],
+ Commit(
+ hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
+ identifier='3.1@eng/example',
+ timestamp=int(time.time()) - 60,
+ author=Contributor('Tim Committer', ['tcommit...@webkit.org']),
+ message='To Be Committed\n\nReviewed by NOBODY (OOPS!).\n',
+ )
+ ]
self.assertEqual(0, program.main(
args=('checkout', 'PR-1'),
@@ -180,13 +183,16 @@
),
],
)]
- repo.commits['eng/example'] = [Commit(
- hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
- identifier='3.1@eng/example',
- timestamp=int(time.time()) - 60,
- author=Contributor('Tim Committer', ['tcommit...@webkit.org']),
- message='To Be Committed\n\nReviewed by NOBODY (OOPS!).\n',
- )]
+ repo.commits['eng/example'] = [
+ repo.commits[repo.default_branch][2],
+ Commit(
+ hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
+ identifier='3.1@eng/example',
+ timestamp=int(time.time()) - 60,
+ author=Contributor('Tim Committer', ['tcommit...@webkit.org']),
+ message='To Be Committed\n\nReviewed by NOBODY (OOPS!).\n',
+ )
+ ]
self.assertEqual(0, program.main(
args=('checkout', 'PR-1'),
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/git_unittest.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -79,7 +79,7 @@
with mocks.local.Git(self.path):
self.assertEqual(
local.Git(self.path).branches,
- ['branch-a', 'branch-b', 'main'],
+ ['branch-a', 'branch-b', 'eng/squash-branch', 'main'],
)
def test_tags(self):
@@ -345,7 +345,7 @@
with mocks.local.Git(self.path, git_svn=True):
self.assertEqual(
run([
- local.Git.executable(), 'log', '--format=fuller', '--no-decorate', '--date=unix', 'branch-b...main',
+ local.Git.executable(), 'log', '--format=fuller', '--no-decorate', '--date=unix', 'main..branch-b',
], cwd=self.path, capture_output=True, encoding='utf-8').stdout,
'''commit 790725a6d79e28db2ecdde29548d2262c0bd059d
Author: Jonathan Bedard <jbed...@apple.com>
@@ -366,6 +366,15 @@
Cherry pick
git-svn-id: https://svn.webkit.org/repository/webkit/trunk@6 268f45cc-cd09-0410-ab3c-d52691b4dbfc
git-svn-id: https://svn.example.org/repository/repository/trunk@5 268f45cc-cd09-0410-ab3c-d52691b4dbfc
+
+commit a30ce8494bf1ac2807a69844f726be4a9843ca55
+Author: Jonathan Bedard <jbed...@apple.com>
+AuthorDate: {time_c}
+Commit: Jonathan Bedard <jbed...@apple.com>
+CommitDate: {time_c}
+
+ 3rd commit
+ git-svn-id: https://svn.example.org/repository/repository/trunk@3 268f45cc-cd09-0410-ab3c-d52691b4dbfc
'''.format(
time_a=1601667000,
time_b=1601664000,
@@ -522,7 +531,7 @@
with mocks.remote.GitHub():
self.assertEqual(
remote.GitHub(self.remote).branches,
- ['branch-a', 'branch-b', 'main'],
+ ['branch-a', 'branch-b', 'eng/squash-branch', 'main'],
)
def test_tags(self):
@@ -661,7 +670,6 @@
]), Commit.Encoder().default(list(git.commits(begin=dict(argument='9b8311f2'), end=dict(argument='621652ad')))))
def test_commits_branch_ref(self):
- self.maxDiff = None
with mocks.remote.GitHub():
git = remote.GitHub(self.remote)
self.assertEqual(
@@ -683,7 +691,7 @@
with mocks.remote.BitBucket():
self.assertEqual(
remote.BitBucket(self.remote).branches,
- ['branch-a', 'branch-b', 'main'],
+ ['branch-a', 'branch-b', 'eng/squash-branch', 'main'],
)
def test_tags(self):
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/land_unittest.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/land_unittest.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/land_unittest.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -34,22 +34,25 @@
def repository(path, has_oops=True, remote=None, git_svn=False, issue_url=None):
branch = 'eng/example'
result = mocks.local.Git(path, remote=remote, git_svn=git_svn)
- result.commits[branch] = [Commit(
- hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
- identifier='3.1@{}'.format(branch),
- revision=10,
- timestamp=int(time.time()) - 60,
- author=Contributor('Tim Committer', ['tcommit...@webkit.org']),
- message='To Be Committed\n{}\nReviewed by {}.\n{}'.format(
- '{}\n'.format(issue_url) if issue_url else '',
- 'NOBODY (OOPS!)' if has_oops else 'Ricky Reviewer',
- '\ngit-svn-id: https://svn.{}/repository/{}/trunk@10 268f45cc-cd09-0410-ab3c-d52691b4dbfc\n'.format(
- result.remote.split('@')[-1].split(':')[0],
- os.path.basename(result.path),
- ) if git_svn else '',
- ),
- )]
- result.head = result.commits[branch][0]
+ result.commits[branch] = [
+ result.commits[result.default_branch][2],
+ Commit(
+ hash='a5fe8afe9bf7d07158fcd9e9732ff02a712db2fd',
+ identifier='3.1@{}'.format(branch),
+ revision=10,
+ timestamp=int(time.time()) - 60,
+ author=Contributor('Tim Committer', ['tcommit...@webkit.org']),
+ message='To Be Committed\n{}\nReviewed by {}.\n{}'.format(
+ '{}\n'.format(issue_url) if issue_url else '',
+ 'NOBODY (OOPS!)' if has_oops else 'Ricky Reviewer',
+ '\ngit-svn-id: https://svn.{}/repository/{}/trunk@10 268f45cc-cd09-0410-ab3c-d52691b4dbfc\n'.format(
+ result.remote.split('@')[-1].split(':')[0],
+ os.path.basename(result.path),
+ ) if git_svn else '',
+ ),
+ )
+ ]
+ result.head = result.commits[branch][-1]
return result
Modified: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py (295387 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py 2022-06-08 18:03:58 UTC (rev 295387)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/pull_request_unittest.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -574,14 +574,17 @@
remotes=dict(fork='https://{}/Contributor/WebKit'.format(remote.hosts[0])),
) as repo, mocks.local.Svn():
- repo.commits['eng/pr-branch'] = [Commit(
- hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
- branch='eng/pr-branch',
- author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
- identifier="5.1@eng/pr-branch",
- timestamp=int(time.time()),
- message='[Testing] Existing commit\nbugs.example.com/show_bug.cgi?id=1'
- )]
+ repo.commits['eng/pr-branch'] = [
+ repo.commits[repo.default_branch][-1],
+ Commit(
+ hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
+ branch='eng/pr-branch',
+ author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
+ identifier="5.1@eng/pr-branch",
+ timestamp=int(time.time()),
+ message='[Testing] Existing commit\nbugs.example.com/show_bug.cgi?id=1'
+ )
+ ]
repo.head = repo.commits['eng/pr-branch'][-1]
self.assertEqual(0, program.main(
args=('pull-request', '-v', '--no-history'),
@@ -698,14 +701,17 @@
) as repo, mocks.local.Svn():
Tracker.instance().issue(1).close(why='Looks like we will not get to this')
- repo.commits['eng/pr-branch'] = [Commit(
- hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
- branch='eng/pr-branch',
- author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
- identifier="5.1@eng/pr-branch",
- timestamp=int(time.time()),
- message='[Testing] Existing commit\nbugs.example.com/show_bug.cgi?id=1'
- )]
+ repo.commits['eng/pr-branch'] = [
+ repo.commits[repo.default_branch][-1],
+ Commit(
+ hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
+ branch='eng/pr-branch',
+ author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
+ identifier="5.1@eng/pr-branch",
+ timestamp=int(time.time()),
+ message='[Testing] Existing commit\nbugs.example.com/show_bug.cgi?id=1'
+ )
+ ]
repo.head = repo.commits['eng/pr-branch'][-1]
self.assertEqual(0, program.main(
args=('pull-request', '-v', '--no-history'),
@@ -943,14 +949,17 @@
self.path, remote='ssh://git@{}/{}/{}.git'.format(remote.hosts[0], remote.project.split('/')[1], remote.project.split('/')[3]),
) as repo, mocks.local.Svn(), Environment(RADAR_USERNAME='tcontributor'), bmocks.Radar(issues=bmocks.ISSUES), patch('webkitbugspy.Tracker._trackers', [radar.Tracker()]):
- repo.commits['eng/pr-branch'] = [Commit(
- hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
- branch='eng/pr-branch',
- author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
- identifier="5.1@eng/pr-branch",
- timestamp=int(time.time()),
- message='<rdar://problem/1> [Testing] Existing commit\n'
- )]
+ repo.commits['eng/pr-branch'] = [
+ repo.commits[repo.default_branch][-1],
+ Commit(
+ hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
+ branch='eng/pr-branch',
+ author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
+ identifier="5.1@eng/pr-branch",
+ timestamp=int(time.time()),
+ message='<rdar://problem/1> [Testing] Existing commit\n'
+ )
+ ]
repo.head = repo.commits['eng/pr-branch'][-1]
self.assertEqual(0, program.main(
args=('pull-request', '-v', '--no-history'),
@@ -992,14 +1001,17 @@
) as repo, mocks.local.Svn(), Environment(RADAR_USERNAME='tcontributor'), bmocks.Radar(issues=bmocks.ISSUES), patch('webkitbugspy.Tracker._trackers', [radar.Tracker()]):
Tracker.instance().issue(1).close(why='Looks like we will not get to this')
- repo.commits['eng/pr-branch'] = [Commit(
- hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
- branch='eng/pr-branch',
- author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
- identifier="5.1@eng/pr-branch",
- timestamp=int(time.time()),
- message='<rdar://problem/1> [Testing] Existing commit\n'
- )]
+ repo.commits['eng/pr-branch'] = [
+ repo.commits[repo.default_branch][-1],
+ Commit(
+ hash='06de5d56554e693db72313f4ca1fb969c30b8ccb',
+ branch='eng/pr-branch',
+ author=dict(name='Tim Contributor', emails=['tcontribu...@example.com']),
+ identifier="5.1@eng/pr-branch",
+ timestamp=int(time.time()),
+ message='<rdar://problem/1> [Testing] Existing commit\n'
+ )
+ ]
repo.head = repo.commits['eng/pr-branch'][-1]
self.assertEqual(0, program.main(
args=('pull-request', '-v', '--no-history'),
Added: trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/squash_unittest.py (0 => 295388)
--- trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/squash_unittest.py (rev 0)
+++ trunk/Tools/Scripts/libraries/webkitscmpy/webkitscmpy/test/squash_unittest.py 2022-06-08 18:13:57 UTC (rev 295388)
@@ -0,0 +1,202 @@
+# Copyright (C) 2022 Apple Inc. All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions
+# are met:
+# 1. Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# 2. Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+#
+# THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+# DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS BE LIABLE FOR ANY
+# DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
+# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
+# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
+# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import logging
+import os
+
+from mock import patch
+from webkitcorepy import OutputCapture, testing
+from webkitscmpy import local, program, mocks
+from webkitcorepy import run
+
+
+class TestSquash(testing.PathTestCase):
+
+ basepath = 'mock/repository'
+ BUGZILLA = 'https://bugs.example.com'
+
+ def setUp(self):
+ super(TestSquash, self).setUp()
+ os.mkdir(os.path.join(self.path, '.git'))
+ os.mkdir(os.path.join(self.path, '.svn'))
+
+ def test_github_with_previous_history(self):
+ with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub() as remote, mocks.local.Git(
+ self.path, remote='https://{}'.format(remote.remote),
+ remotes=dict(fork='https://{}/Contributor/WebKit'.format(remote.hosts[0])),
+ ) as repo, mocks.local.Svn(), patch('webkitbugspy.Tracker._trackers', []):
+ repo.checkout('eng/squash-branch')
+ result = program.main(
+ args=('pull-request', '-v', '--squash', '--no-history'),
+ path=self.path,
+ )
+ self.assertEqual(0, result)
+ self.assertDictEqual(repo.modified, dict())
+ self.assertDictEqual(repo.staged, dict())
+ self.assertEqual(True, '[Testing] Creating commits' in repo.head.message)
+ self.assertEqual(True, '* a.cpp' in repo.head.message)
+ self.assertEqual(True, '* b.cpp' in repo.head.message)
+ self.assertEqual(True, '* c.cpp' in repo.head.message)
+ self.assertEqual(True, 'Changed something 1' in repo.head.message)
+ self.assertEqual(True, 'Changed something 2' in repo.head.message)
+ self.assertEqual(True, 'Changed something 3' in repo.head.message)
+ self.assertEqual(local.Git(self.path).remote().pull_requests.get(1).draft, False)
+
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ "Created 'PR 1 | [Testing] Creating commits'!\n"
+ "https://github.example.com/WebKit/WebKit/pull/1\n",
+ )
+ self.assertEqual(captured.stderr.getvalue(), '')
+ log = captured.root.log.getvalue().splitlines()
+ self.assertEqual(
+ [line for line in log if 'Mock process' not in line], [
+ ' Found 1 commit...',
+ ' Found 2 commits...',
+ ' Found 3 commits...',
+ ' Squashed 3 commits',
+ "Rebasing 'eng/squash-branch' on 'main'...",
+ "Rebased 'eng/squash-branch' on 'main!'",
+ " Found 1 commit...",
+ 'Running pre-PR checks...',
+ 'No pre-PR checks to run',
+ "Pushing 'eng/squash-branch' to 'fork'...",
+ "Syncing 'main' to remote 'fork'",
+ "Creating pull-request for 'eng/squash-branch'..."
+ ],
+ )
+
+ def test_github_without_previous_history(self):
+ with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub() as remote, mocks.local.Git(
+ self.path, remote='https://{}'.format(remote.remote),
+ remotes=dict(fork='https://{}/Contributor/WebKit'.format(remote.hosts[0])),
+ ) as repo, mocks.local.Svn(), patch('webkitbugspy.Tracker._trackers', []):
+ repo.checkout('eng/squash-branch')
+ result = program.main(
+ args=('pull-request', '-v', '--squash', '--no-history', '--new-message'),
+ path=self.path,
+ )
+ self.assertEqual(0, result)
+ self.assertDictEqual(repo.modified, dict())
+ self.assertDictEqual(repo.staged, dict())
+ self.assertEqual(True, '[Testing] Creating commits' in repo.head.message)
+ self.assertEqual(True, '* a.cpp' in repo.head.message)
+ self.assertEqual(True, '* b.cpp' in repo.head.message)
+ self.assertEqual(True, '* c.cpp' in repo.head.message)
+ self.assertEqual(True, 'Changed something 1' not in repo.head.message)
+ self.assertEqual(True, 'Changed something 2' not in repo.head.message)
+ self.assertEqual(True, 'Changed something 3' not in repo.head.message)
+ self.assertEqual(local.Git(self.path).remote().pull_requests.get(1).draft, False)
+
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ "Created 'PR 1 | [Testing] Creating commits'!\n"
+ "https://github.example.com/WebKit/WebKit/pull/1\n",
+ )
+ self.assertEqual(captured.stderr.getvalue(), '')
+ log = captured.root.log.getvalue().splitlines()
+ self.assertEqual(
+ [line for line in log if 'Mock process' not in line], [
+ ' Found 1 commit...',
+ ' Found 2 commits...',
+ ' Found 3 commits...',
+ ' Squashed 3 commits',
+ "Rebasing 'eng/squash-branch' on 'main'...",
+ "Rebased 'eng/squash-branch' on 'main!'",
+ " Found 1 commit...",
+ 'Running pre-PR checks...',
+ 'No pre-PR checks to run',
+ "Pushing 'eng/squash-branch' to 'fork'...",
+ "Syncing 'main' to remote 'fork'",
+ "Creating pull-request for 'eng/squash-branch'..."
+ ],
+ )
+
+ def test_github_two_step(self):
+ with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub() as remote, mocks.local.Git(
+ self.path, remote='https://{}'.format(remote.remote),
+ remotes=dict(fork='https://{}/Contributor/WebKit'.format(remote.hosts[0])),
+ ) as repo, mocks.local.Svn(), patch('webkitbugspy.Tracker._trackers', []):
+ repo.checkout('eng/squash-branch')
+ result = program.main(
+ args=('squash', '-v'),
+ path=self.path,
+ )
+ self.assertEqual(0, result)
+ self.assertDictEqual(repo.modified, dict())
+ self.assertDictEqual(repo.staged, dict())
+ self.assertEqual(True, '[Testing] Creating commits' in repo.head.message)
+ result = program.main(args=('pull-request', '-v', '--no-history'), path=self.path)
+ self.assertEqual(0, result)
+ self.assertEqual(local.Git(self.path).remote().pull_requests.get(1).draft, False)
+
+ self.assertEqual(
+ captured.stdout.getvalue(),
+ "Created 'PR 1 | [Testing] Creating commits'!\n"
+ "https://github.example.com/WebKit/WebKit/pull/1\n",
+ )
+ self.assertEqual(captured.stderr.getvalue(), '')
+ log = captured.root.log.getvalue().splitlines()
+ self.assertEqual(
+ [line for line in log if 'Mock process' not in line], [
+ ' Found 1 commit...',
+ ' Found 2 commits...',
+ ' Found 3 commits...',
+ ' Squashed 3 commits',
+ ' Found 1 commit...',
+ 'Using committed changes...',
+ "Rebasing 'eng/squash-branch' on 'main'...",
+ "Rebased 'eng/squash-branch' on 'main!'",
+ " Found 1 commit...",
+ 'Running pre-PR checks...',
+ 'No pre-PR checks to run',
+ "Pushing 'eng/squash-branch' to 'fork'...",
+ "Syncing 'main' to remote 'fork'",
+ "Creating pull-request for 'eng/squash-branch'..."
+ ],
+ )
+
+ def test_modified(self):
+ with OutputCapture(level=logging.INFO) as captured, mocks.remote.GitHub() as remote, \
+ mocks.local.Git(self.path, remote='https://{}'.format(remote.remote),) as repo, mocks.local.Svn(), \
+ patch('webkitbugspy.Tracker._trackers', []):
+
+ repo.modified = {
+ 'a.py': """diff --git a/a.py b/a.py
+index 05e8751..0bf3c85 100644
+--- a/test
++++ b/test
+@@ -1,3 +1,4 @@
++1111
+ aaaa
+ cccc
+ bbbb
+"""
+ }
+ repo.checkout('eng/squash-branch')
+ result = program.main(
+ args=('squash', '-v'),
+ path=self.path,
+ )
+ self.assertEqual(1, result)
+
+ self.assertEqual(captured.stderr.getvalue(), 'Please commit your changes or stash them before you squash to base commit: d8bce26fa65c')