Merge lp:~cjwatson/turnip/merge-prerequisite into lp:turnip

Proposed by Colin Watson
Status: Merged
Merged at revision: 182
Proposed branch: lp:~cjwatson/turnip/merge-prerequisite
Merge into: lp:turnip
Diff against target: 141 lines (+68/-17)
3 files modified
turnip/api/store.py (+47/-16)
turnip/api/tests/test_api.py (+18/-0)
turnip/api/views.py (+3/-1)
To merge this branch: bzr merge lp:~cjwatson/turnip/merge-prerequisite
Reviewer Review Type Date Requested Status
William Grant code Approve
Review via email: mp+269512@code.launchpad.net

Commit message

Add an optional sha1_prerequisite parameter to compare-merge API.

Description of the change

Add an optional sha1_prerequisite parameter to compare-merge API.

This involves writing a temporary tree object, so I made sure to use an ephemeral repository in this case in order that we aren't relying on GC to clean things up for us later. The algorithm is the same as that used by Launchpad for Bazaar: diff(merge prerequisite into target, merge source into target).

To post a comment you must log in.
Revision history for this message
William Grant (wgrant) :
review: Approve (code)
183. By Colin Watson

Get the file mode for a conflict from the first suitable existing IndexEntry.

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
1=== modified file 'turnip/api/store.py'
2--- turnip/api/store.py 2015-08-28 11:55:50 +0000
3+++ turnip/api/store.py 2015-09-07 16:18:46 +0000
4@@ -177,14 +177,17 @@
5
6
7 @contextmanager
8-def open_repo(repo_store, repo_name):
9+def open_repo(repo_store, repo_name, force_ephemeral=False):
10 """Open an existing git repository. Optionally create an ephemeral
11- repository with alternates if repo_path contains ':'.
12+ repository with alternates if repo_name contains ':' or force_ephemeral
13+ is True.
14
15 :param repo_store: path to repository root.
16 :param repo_name: repository name.
17+ :param force_ephemeral: create an ephemeral repository even if repo_name
18+ does not contain ':'.
19 """
20- if ':' in repo_name:
21+ if force_ephemeral or ':' in repo_name:
22 try:
23 # Create ephemeral repo with alternates set from both.
24 # Neither git nor libgit2 will respect a relative alternate
25@@ -297,28 +300,56 @@
26 sha1_source, context_lines)
27
28
29+def _add_conflicted_files(repo, index):
30+ """Add flattened versions of conflicted files in an index.
31+
32+ Any conflicted files will be merged using
33+ `pygit2.Repository.merge_file_from_index` (thereby including conflict
34+ markers); the resulting files will be added to the index and the
35+ conflicts deleted.
36+
37+ :param repo: a `pygit2.Repository`.
38+ :param index: a `pygit2.Index` to modify.
39+ :return: a set of files that contain conflicts.
40+ """
41+ conflicts = set()
42+ if index.conflicts is not None:
43+ for conflict in list(index.conflicts):
44+ conflict_entry = [
45+ entry for entry in conflict if entry is not None][0]
46+ path = conflict_entry.path
47+ conflicts.add(path)
48+ merged_file = repo.merge_file_from_index(*conflict)
49+ blob_oid = repo.create_blob(merged_file)
50+ index.add(IndexEntry(path, blob_oid, conflict_entry.mode))
51+ del index.conflicts[path]
52+ return conflicts
53+
54+
55 def get_merge_diff(repo_store, repo_name, sha1_base,
56- sha1_head, context_lines=3):
57+ sha1_head, context_lines=3, sha1_prerequisite=None):
58 """Get diff of common ancestor and source diff.
59
60 :param sha1_base: target sha1 for merge.
61 :param sha1_head: source sha1 for merge.
62 :param context_lines: num unchanged lines that define a hunk boundary.
63+ :param sha1_prerequisite: if not None, sha1 of prerequisite commit to
64+ merge into `sha1_target` before computing diff to `sha1_source`.
65 """
66- with open_repo(repo_store, repo_name) as repo:
67+ with open_repo(
68+ repo_store, repo_name,
69+ force_ephemeral=(sha1_prerequisite is not None)) as repo:
70+ if sha1_prerequisite is not None:
71+ prerequisite_index = repo.merge_commits(
72+ sha1_base, sha1_prerequisite)
73+ _add_conflicted_files(repo, prerequisite_index)
74+ from_tree = repo[prerequisite_index.write_tree(repo=repo)]
75+ else:
76+ from_tree = repo[sha1_base].tree
77 merged_index = repo.merge_commits(sha1_base, sha1_head)
78- conflicts = set()
79- if merged_index.conflicts is not None:
80- for conflict in list(merged_index.conflicts):
81- path = [entry for entry in conflict
82- if entry is not None][0].path
83- conflicts.add(path)
84- merged_file = repo.merge_file_from_index(*conflict)
85- blob_oid = repo.create_blob(merged_file)
86- merged_index.add(IndexEntry(path, blob_oid, GIT_FILEMODE_BLOB))
87- del merged_index.conflicts[path]
88+ conflicts = _add_conflicted_files(repo, merged_index)
89 patch = merged_index.diff_to_tree(
90- repo[sha1_base].tree, context_lines=context_lines).patch
91+ from_tree, context_lines=context_lines).patch
92 if patch is None:
93 patch = u''
94 shas = [sha1_base, sha1_head]
95
96=== modified file 'turnip/api/tests/test_api.py'
97--- turnip/api/tests/test_api.py 2015-06-18 00:05:01 +0000
98+++ turnip/api/tests/test_api.py 2015-09-07 16:18:46 +0000
99@@ -382,6 +382,24 @@
100 """), resp.json_body['patch'])
101 self.assertEqual(['blah.txt'], resp.json_body['conflicts'])
102
103+ def test_repo_diff_merge_with_prerequisite(self):
104+ """Ensure that compare-merge handles prerequisites."""
105+ repo = RepoFactory(self.repo_store)
106+ c1 = repo.add_commit('foo\n', 'blah.txt')
107+ c2 = repo.add_commit('foo\nbar\n', 'blah.txt', parents=[c1])
108+ c3 = repo.add_commit('foo\nbar\nbaz\n', 'blah.txt', parents=[c2])
109+
110+ resp = self.app.get(
111+ '/repo/{}/compare-merge/{}:{}?sha1_prerequisite={}'.format(
112+ self.repo_path, c1, c3, c2))
113+ self.assertIn(dedent("""\
114+ @@ -1,2 +1,3 @@
115+ foo
116+ bar
117+ +baz
118+ """), resp.json_body['patch'])
119+ self.assertEqual([], resp.json_body['conflicts'])
120+
121 def test_repo_diff_merge_empty(self):
122 """Ensure that diffing two identical commits returns an empty string
123 as the patch, not None."""
124
125=== modified file 'turnip/api/views.py'
126--- turnip/api/views.py 2015-06-15 19:15:51 +0000
127+++ turnip/api/views.py 2015-09-07 16:18:46 +0000
128@@ -230,10 +230,12 @@
129 def get(self, repo_store, repo_name):
130 """Returns diff of two commits."""
131 context_lines = int(self.request.params.get('context_lines', 3))
132+ sha1_prerequisite = self.request.params.get('sha1_prerequisite')
133 try:
134 patch = store.get_merge_diff(
135 repo_store, repo_name, self.request.matchdict['base'],
136- self.request.matchdict['head'], context_lines)
137+ self.request.matchdict['head'], context_lines=context_lines,
138+ sha1_prerequisite=sha1_prerequisite)
139 except (KeyError, ValueError, GitError):
140 # invalid pygit2 sha1's return ValueError: 1: Ambiguous lookup
141 return exc.HTTPNotFound()

Subscribers

People subscribed via source and target branches

to all changes: