xlgit 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- xlgit-0.1.0/LICENSE +202 -0
- xlgit-0.1.0/NOTICE +5 -0
- xlgit-0.1.0/PKG-INFO +214 -0
- xlgit-0.1.0/README.md +189 -0
- xlgit-0.1.0/pyproject.toml +39 -0
- xlgit-0.1.0/setup.cfg +4 -0
- xlgit-0.1.0/tests/test_cell_types.py +137 -0
- xlgit-0.1.0/tests/test_cli.py +206 -0
- xlgit-0.1.0/tests/test_merge.py +263 -0
- xlgit-0.1.0/tests/test_row_guard.py +63 -0
- xlgit-0.1.0/tests/test_row_merge.py +79 -0
- xlgit-0.1.0/tests/test_rows.py +92 -0
- xlgit-0.1.0/tests/test_tables_pivots.py +306 -0
- xlgit-0.1.0/xlgit.egg-info/PKG-INFO +214 -0
- xlgit-0.1.0/xlgit.egg-info/SOURCES.txt +18 -0
- xlgit-0.1.0/xlgit.egg-info/dependency_links.txt +1 -0
- xlgit-0.1.0/xlgit.egg-info/entry_points.txt +2 -0
- xlgit-0.1.0/xlgit.egg-info/requires.txt +2 -0
- xlgit-0.1.0/xlgit.egg-info/top_level.txt +1 -0
- xlgit-0.1.0/xlgit.py +3357 -0
xlgit-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
|
|
2
|
+
Apache License
|
|
3
|
+
Version 2.0, January 2004
|
|
4
|
+
http://www.apache.org/licenses/
|
|
5
|
+
|
|
6
|
+
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
|
7
|
+
|
|
8
|
+
1. Definitions.
|
|
9
|
+
|
|
10
|
+
"License" shall mean the terms and conditions for use, reproduction,
|
|
11
|
+
and distribution as defined by Sections 1 through 9 of this document.
|
|
12
|
+
|
|
13
|
+
"Licensor" shall mean the copyright owner or entity authorized by
|
|
14
|
+
the copyright owner that is granting the License.
|
|
15
|
+
|
|
16
|
+
"Legal Entity" shall mean the union of the acting entity and all
|
|
17
|
+
other entities that control, are controlled by, or are under common
|
|
18
|
+
control with that entity. For the purposes of this definition,
|
|
19
|
+
"control" means (i) the power, direct or indirect, to cause the
|
|
20
|
+
direction or management of such entity, whether by contract or
|
|
21
|
+
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
|
22
|
+
outstanding shares, or (iii) beneficial ownership of such entity.
|
|
23
|
+
|
|
24
|
+
"You" (or "Your") shall mean an individual or Legal Entity
|
|
25
|
+
exercising permissions granted by this License.
|
|
26
|
+
|
|
27
|
+
"Source" form shall mean the preferred form for making modifications,
|
|
28
|
+
including but not limited to software source code, documentation
|
|
29
|
+
source, and configuration files.
|
|
30
|
+
|
|
31
|
+
"Object" form shall mean any form resulting from mechanical
|
|
32
|
+
transformation or translation of a Source form, including but
|
|
33
|
+
not limited to compiled object code, generated documentation,
|
|
34
|
+
and conversions to other media types.
|
|
35
|
+
|
|
36
|
+
"Work" shall mean the work of authorship, whether in Source or
|
|
37
|
+
Object form, made available under the License, as indicated by a
|
|
38
|
+
copyright notice that is included in or attached to the work
|
|
39
|
+
(an example is provided in the Appendix below).
|
|
40
|
+
|
|
41
|
+
"Derivative Works" shall mean any work, whether in Source or Object
|
|
42
|
+
form, that is based on (or derived from) the Work and for which the
|
|
43
|
+
editorial revisions, annotations, elaborations, or other modifications
|
|
44
|
+
represent, as a whole, an original work of authorship. For the purposes
|
|
45
|
+
of this License, Derivative Works shall not include works that remain
|
|
46
|
+
separable from, or merely link (or bind by name) to the interfaces of,
|
|
47
|
+
the Work and Derivative Works thereof.
|
|
48
|
+
|
|
49
|
+
"Contribution" shall mean any work of authorship, including
|
|
50
|
+
the original version of the Work and any modifications or additions
|
|
51
|
+
to that Work or Derivative Works thereof, that is intentionally
|
|
52
|
+
submitted to Licensor for inclusion in the Work by the copyright owner
|
|
53
|
+
or by an individual or Legal Entity authorized to submit on behalf of
|
|
54
|
+
the copyright owner. For the purposes of this definition, "submitted"
|
|
55
|
+
means any form of electronic, verbal, or written communication sent
|
|
56
|
+
to the Licensor or its representatives, including but not limited to
|
|
57
|
+
communication on electronic mailing lists, source code control systems,
|
|
58
|
+
and issue tracking systems that are managed by, or on behalf of, the
|
|
59
|
+
Licensor for the purpose of discussing and improving the Work, but
|
|
60
|
+
excluding communication that is conspicuously marked or otherwise
|
|
61
|
+
designated in writing by the copyright owner as "Not a Contribution."
|
|
62
|
+
|
|
63
|
+
"Contributor" shall mean Licensor and any individual or Legal Entity
|
|
64
|
+
on behalf of whom a Contribution has been received by Licensor and
|
|
65
|
+
subsequently incorporated within the Work.
|
|
66
|
+
|
|
67
|
+
2. Grant of Copyright License. Subject to the terms and conditions of
|
|
68
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
69
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
70
|
+
copyright license to reproduce, prepare Derivative Works of,
|
|
71
|
+
publicly display, publicly perform, sublicense, and distribute the
|
|
72
|
+
Work and such Derivative Works in Source or Object form.
|
|
73
|
+
|
|
74
|
+
3. Grant of Patent License. Subject to the terms and conditions of
|
|
75
|
+
this License, each Contributor hereby grants to You a perpetual,
|
|
76
|
+
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
|
77
|
+
(except as stated in this section) patent license to make, have made,
|
|
78
|
+
use, offer to sell, sell, import, and otherwise transfer the Work,
|
|
79
|
+
where such license applies only to those patent claims licensable
|
|
80
|
+
by such Contributor that are necessarily infringed by their
|
|
81
|
+
Contribution(s) alone or by combination of their Contribution(s)
|
|
82
|
+
with the Work to which such Contribution(s) was submitted. If You
|
|
83
|
+
institute patent litigation against any entity (including a
|
|
84
|
+
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
|
85
|
+
or a Contribution incorporated within the Work constitutes direct
|
|
86
|
+
or contributory patent infringement, then any patent licenses
|
|
87
|
+
granted to You under this License for that Work shall terminate
|
|
88
|
+
as of the date such litigation is filed.
|
|
89
|
+
|
|
90
|
+
4. Redistribution. You may reproduce and distribute copies of the
|
|
91
|
+
Work or Derivative Works thereof in any medium, with or without
|
|
92
|
+
modifications, and in Source or Object form, provided that You
|
|
93
|
+
meet the following conditions:
|
|
94
|
+
|
|
95
|
+
(a) You must give any other recipients of the Work or
|
|
96
|
+
Derivative Works a copy of this License; and
|
|
97
|
+
|
|
98
|
+
(b) You must cause any modified files to carry prominent notices
|
|
99
|
+
stating that You changed the files; and
|
|
100
|
+
|
|
101
|
+
(c) You must retain, in the Source form of any Derivative Works
|
|
102
|
+
that You distribute, all copyright, patent, trademark, and
|
|
103
|
+
attribution notices from the Source form of the Work,
|
|
104
|
+
excluding those notices that do not pertain to any part of
|
|
105
|
+
the Derivative Works; and
|
|
106
|
+
|
|
107
|
+
(d) If the Work includes a "NOTICE" text file as part of its
|
|
108
|
+
distribution, then any Derivative Works that You distribute must
|
|
109
|
+
include a readable copy of the attribution notices contained
|
|
110
|
+
within such NOTICE file, excluding those notices that do not
|
|
111
|
+
pertain to any part of the Derivative Works, in at least one
|
|
112
|
+
of the following places: within a NOTICE text file distributed
|
|
113
|
+
as part of the Derivative Works; within the Source form or
|
|
114
|
+
documentation, if provided along with the Derivative Works; or,
|
|
115
|
+
within a display generated by the Derivative Works, if and
|
|
116
|
+
wherever such third-party notices normally appear. The contents
|
|
117
|
+
of the NOTICE file are for informational purposes only and
|
|
118
|
+
do not modify the License. You may add Your own attribution
|
|
119
|
+
notices within Derivative Works that You distribute, alongside
|
|
120
|
+
or as an addendum to the NOTICE text from the Work, provided
|
|
121
|
+
that such additional attribution notices cannot be construed
|
|
122
|
+
as modifying the License.
|
|
123
|
+
|
|
124
|
+
You may add Your own copyright statement to Your modifications and
|
|
125
|
+
may provide additional or different license terms and conditions
|
|
126
|
+
for use, reproduction, or distribution of Your modifications, or
|
|
127
|
+
for any such Derivative Works as a whole, provided Your use,
|
|
128
|
+
reproduction, and distribution of the Work otherwise complies with
|
|
129
|
+
the conditions stated in this License.
|
|
130
|
+
|
|
131
|
+
5. Submission of Contributions. Unless You explicitly state otherwise,
|
|
132
|
+
any Contribution intentionally submitted for inclusion in the Work
|
|
133
|
+
by You to the Licensor shall be under the terms and conditions of
|
|
134
|
+
this License, without any additional terms or conditions.
|
|
135
|
+
Notwithstanding the above, nothing herein shall supersede or modify
|
|
136
|
+
the terms of any separate license agreement you may have executed
|
|
137
|
+
with Licensor regarding such Contributions.
|
|
138
|
+
|
|
139
|
+
6. Trademarks. This License does not grant permission to use the trade
|
|
140
|
+
names, trademarks, service marks, or product names of the Licensor,
|
|
141
|
+
except as required for reasonable and customary use in describing the
|
|
142
|
+
origin of the Work and reproducing the content of the NOTICE file.
|
|
143
|
+
|
|
144
|
+
7. Disclaimer of Warranty. Unless required by applicable law or
|
|
145
|
+
agreed to in writing, Licensor provides the Work (and each
|
|
146
|
+
Contributor provides its Contributions) on an "AS IS" BASIS,
|
|
147
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
|
148
|
+
implied, including, without limitation, any warranties or conditions
|
|
149
|
+
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
|
150
|
+
PARTICULAR PURPOSE. You are solely responsible for determining the
|
|
151
|
+
appropriateness of using or redistributing the Work and assume any
|
|
152
|
+
risks associated with Your exercise of permissions under this License.
|
|
153
|
+
|
|
154
|
+
8. Limitation of Liability. In no event and under no legal theory,
|
|
155
|
+
whether in tort (including negligence), contract, or otherwise,
|
|
156
|
+
unless required by applicable law (such as deliberate and grossly
|
|
157
|
+
negligent acts) or agreed to in writing, shall any Contributor be
|
|
158
|
+
liable to You for damages, including any direct, indirect, special,
|
|
159
|
+
incidental, or consequential damages of any character arising as a
|
|
160
|
+
result of this License or out of the use or inability to use the
|
|
161
|
+
Work (including but not limited to damages for loss of goodwill,
|
|
162
|
+
work stoppage, computer failure or malfunction, or any and all
|
|
163
|
+
other commercial damages or losses), even if such Contributor
|
|
164
|
+
has been advised of the possibility of such damages.
|
|
165
|
+
|
|
166
|
+
9. Accepting Warranty or Additional Liability. While redistributing
|
|
167
|
+
the Work or Derivative Works thereof, You may choose to offer,
|
|
168
|
+
and charge a fee for, acceptance of support, warranty, indemnity,
|
|
169
|
+
or other liability obligations and/or rights consistent with this
|
|
170
|
+
License. However, in accepting such obligations, You may act only
|
|
171
|
+
on Your own behalf and on Your sole responsibility, not on behalf
|
|
172
|
+
of any other Contributor, and only if You agree to indemnify,
|
|
173
|
+
defend, and hold each Contributor harmless for any liability
|
|
174
|
+
incurred by, or claims asserted against, such Contributor by reason
|
|
175
|
+
of your accepting any such warranty or additional liability.
|
|
176
|
+
|
|
177
|
+
END OF TERMS AND CONDITIONS
|
|
178
|
+
|
|
179
|
+
APPENDIX: How to apply the Apache License to your work.
|
|
180
|
+
|
|
181
|
+
To apply the Apache License to your work, attach the following
|
|
182
|
+
boilerplate notice, with the fields enclosed by brackets "[]"
|
|
183
|
+
replaced with your own identifying information. (Don't include
|
|
184
|
+
the brackets!) The text should be enclosed in the appropriate
|
|
185
|
+
comment syntax for the file format. We also recommend that a
|
|
186
|
+
file or class name and description of purpose be included on the
|
|
187
|
+
same "printed page" as the copyright notice for easier
|
|
188
|
+
identification within third-party archives.
|
|
189
|
+
|
|
190
|
+
Copyright [yyyy] [name of copyright owner]
|
|
191
|
+
|
|
192
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
193
|
+
you may not use this file except in compliance with the License.
|
|
194
|
+
You may obtain a copy of the License at
|
|
195
|
+
|
|
196
|
+
http://www.apache.org/licenses/LICENSE-2.0
|
|
197
|
+
|
|
198
|
+
Unless required by applicable law or agreed to in writing, software
|
|
199
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
200
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
201
|
+
See the License for the specific language governing permissions and
|
|
202
|
+
limitations under the License.
|
xlgit-0.1.0/NOTICE
ADDED
xlgit-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: xlgit
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Cell-level diff and merge for Excel workbooks in git, keeping charts, tables and pivots
|
|
5
|
+
Author: Michael Fowler
|
|
6
|
+
License-Expression: Apache-2.0
|
|
7
|
+
Project-URL: Repository, https://github.com/MichaelFowler1/excel-git
|
|
8
|
+
Project-URL: Issues, https://github.com/MichaelFowler1/excel-git/issues
|
|
9
|
+
Keywords: excel,git,merge,diff,xlsx,spreadsheet,version-control
|
|
10
|
+
Classifier: Development Status :: 4 - Beta
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Intended Audience :: Financial and Insurance Industry
|
|
14
|
+
Classifier: Operating System :: OS Independent
|
|
15
|
+
Classifier: Programming Language :: Python :: 3
|
|
16
|
+
Classifier: Topic :: Software Development :: Version Control :: Git
|
|
17
|
+
Classifier: Topic :: Office/Business :: Financial :: Spreadsheet
|
|
18
|
+
Requires-Python: >=3.9
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
License-File: NOTICE
|
|
22
|
+
Requires-Dist: openpyxl>=3.1.0
|
|
23
|
+
Requires-Dist: lxml>=4.9
|
|
24
|
+
Dynamic: license-file
|
|
25
|
+
|
|
26
|
+
# xlgit
|
|
27
|
+
|
|
28
|
+
> Beta (0.1.0). Every release is fuzz-tested against thousands of real-world workbooks, but you'll still find cases it gets wrong. Git keeps every version, so a bad merge can always be undone. Please [open an issue](https://github.com/MichaelFowler1/excel-git/issues) when something looks wrong.
|
|
29
|
+
|
|
30
|
+
Git and GitHub treat `.xlsx` as an opaque binary blob. You can commit, fork and branch it, but a diff just says "binary file changed" and any merge where both sides touched the file is a conflict. This fixes that.
|
|
31
|
+
|
|
32
|
+
## What you get
|
|
33
|
+
|
|
34
|
+
| | Without xlgit | With xlgit |
|
|
35
|
+
|---|---|---|
|
|
36
|
+
| `git diff` | `Binary files differ` | `changed Budget!B2 1000 -> 1100`, plus inserted, deleted and moved rows, and chart, image and comment changes |
|
|
37
|
+
| Seeing changes | n/a | `xlgit diff --html`: the sheet as a grid in your browser, changes highlighted |
|
|
38
|
+
| Merge, different cells edited | conflict, pick one whole file | merges cleanly |
|
|
39
|
+
| Merge, one side inserted rows | conflict | the other side's edits follow their rows |
|
|
40
|
+
| Merge, same cell edited | conflict | conflict on just that cell, listed in a `_merge_conflicts` sheet with a link to it |
|
|
41
|
+
| Charts, images, comments, formatting, macros | n/a | kept, and their edits to them carried over |
|
|
42
|
+
| Tables and pivot tables | n/a | merged: you add rows, they add a column, you get both |
|
|
43
|
+
| Pull request on GitHub | "binary file not shown" | bot comment with a table of every changed cell, chart, table and pivot |
|
|
44
|
+
|
|
45
|
+
Formulas are compared as formulas (`=B2+C2`), not their cached results.
|
|
46
|
+
|
|
47
|
+
## Get started
|
|
48
|
+
|
|
49
|
+
You need git. Then, once per computer, either:
|
|
50
|
+
|
|
51
|
+
**With Python** (3.9 or newer):
|
|
52
|
+
|
|
53
|
+
```bash
|
|
54
|
+
pip install xlgit
|
|
55
|
+
xlgit install
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
**Without Python:** download the program for your computer from the [latest release](https://github.com/MichaelFowler1/excel-git/releases/latest) (`xlgit-windows.exe`, `xlgit-macos-arm64` for Apple silicon, `xlgit-macos-intel`, `xlgit-linux`), rename it to `xlgit` (`xlgit.exe` on Windows), put it somewhere it will stay, and run `xlgit install`. On Windows you can also just double-click it and it offers to set itself up. The downloads aren't code-signed yet: on Windows click "More info" then "Run anyway"; on a Mac, right-click it, choose Open, then Open again. If you move the program later, run `xlgit install` again.
|
|
59
|
+
|
|
60
|
+
That's it. Every git repository on this computer now understands `.xlsx` and `.xlsm` files, including ones you clone or create later. Keep using git the way you already do.
|
|
61
|
+
|
|
62
|
+
To also get a comment listing the changed cells on every GitHub pull request, run this once inside the repository and push:
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
xlgit install --github
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
Run `xlgit` on its own at any time to see the commands and check that everything is set up.
|
|
69
|
+
|
|
70
|
+
## Everyday use
|
|
71
|
+
|
|
72
|
+
**See what changed.** `xlgit diff` lists every cell that changed in your workbooks since the last commit. Inserted, deleted and moved rows show up as rows, not as every cell below them changing. `git diff`, `git log -p` and `git show` show cell changes too.
|
|
73
|
+
|
|
74
|
+
```
|
|
75
|
+
$ xlgit diff
|
|
76
|
+
=== budget.xlsx ===
|
|
77
|
+
changed Budget!C3 350 -> 999
|
|
78
|
+
row inserted Budget row 4 A: 'Gas', B: 60, C: 70, D: =B4+C4
|
|
79
|
+
changed Budget!D8 =SUM(D2:D6) -> =SUM(D2:D7)
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`xlgit diff --html` opens the same changes in your browser, laid out like the spreadsheet:
|
|
83
|
+
|
|
84
|
+
<img src="docs/visual-diff.png" alt="Visual diff: a changed cell shows its old value, an inserted row is green, a changed SUM range is highlighted" width="480">
|
|
85
|
+
|
|
86
|
+
**Merge.** `git merge` and `git pull` combine edits from both branches cell by cell. If you changed different cells, there's nothing to do.
|
|
87
|
+
|
|
88
|
+
**When both of you changed the same cell**, git stops and xlgit tells you which cells:
|
|
89
|
+
|
|
90
|
+
```
|
|
91
|
+
xlgit merged budget.xlsx: took 3 cell(s) from the other branch, but 1 change(s) clash.
|
|
92
|
+
Budget B2: yours 1100, theirs 1200 (was 1000)
|
|
93
|
+
Your values were kept. Every clash is listed, with a link, on the sheet '_merge_conflicts'.
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
Open the workbook, go through the `_merge_conflicts` sheet (each row links to its cell), fix the cells, delete that sheet, save, then run `git add budget.xlsx` and `git commit`. Git keeps every version, so nothing is ever lost: `git merge --abort` undoes the whole merge.
|
|
97
|
+
|
|
98
|
+
## Found a problem?
|
|
99
|
+
|
|
100
|
+
Please [open an issue](https://github.com/MichaelFowler1/excel-git/issues/new/choose). Bug reports on real workbooks are the most useful thing you can give this project, and you don't have to share your data to do it:
|
|
101
|
+
|
|
102
|
+
```bash
|
|
103
|
+
xlgit scrub --merge budget.xlsx # during a merge that went wrong: base, yours, theirs, in one zip
|
|
104
|
+
xlgit scrub old.xlsx new.xlsx # any workbooks, e.g. for a wrong diff
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
`scrub` makes copies where every number, piece of text, comment, chart label and file property is replaced with made-up values, keeping formulas, layout, charts, tables and pivots, so the problem still shows up. Equal values stay equal across the files scrubbed together. Sheet names and named ranges are kept (formulas refer to them), macros are removed, and images are replaced with blank ones. Open the copies and check them before you share them.
|
|
108
|
+
|
|
109
|
+
## How the merge works
|
|
110
|
+
|
|
111
|
+
A workbook is a zip of XML files: one per sheet, one per chart, one per image and so on. Instead of re-saving the whole thing through a spreadsheet library (which is how charts used to get lost), xlgit starts from your copy's zip and only rewrites the XML that has to change:
|
|
112
|
+
|
|
113
|
+
- **Cells**: 3-way merge cell by cell. A cell only one side changed takes that side's value.
|
|
114
|
+
- **Rows**: if one branch inserted, deleted or moved rows and the other edited cells, the edits land on the rows where their cells ended up, with formula references renumbered the way Excel does it. An edit to a row the other branch deleted is a conflict.
|
|
115
|
+
- **Charts, images, comments, macros**: 3-way merge object by object. If only their branch changed a chart, you get their version. If both did, yours is kept and it's flagged as a conflict. Chart edits caused by cell changes (Excel caches plotted values inside the chart) don't count as edits.
|
|
116
|
+
- **Tables**: merged field by field. Their new column plus your new rows gives a table with both. Tables added on their branch come over, and if both branches added a `Table2`, theirs is renamed `Table3` and their formulas are updated to match.
|
|
117
|
+
- **Pivot tables**: a pivot, its data cache and the cached records merge as one bundle. A branch that only refreshed a pivot (same layout, new data) doesn't count as an edit, so your data change and their "switch to Average" merge cleanly. New pivots from their branch come over, sharing an existing cache when they used one.
|
|
118
|
+
- **Sheets**: new sheets on their branch come over whole, with their charts, tables and pivots. Renames and deletes merge too.
|
|
119
|
+
- **Named ranges**: merged by name.
|
|
120
|
+
|
|
121
|
+
Writers renumber a workbook's internal files on every save (add a chart to an early sheet and every later `chart1.xml` becomes `chart2.xml`). xlgit matches objects by what they are, like "the chart called Chart 1 on sheet Notes" or "table id 3", not by file name, so renumbering doesn't cause false conflicts.
|
|
122
|
+
|
|
123
|
+
Excel recalculates every formula and refreshes affected pivot tables when it opens the merged file.
|
|
124
|
+
|
|
125
|
+
## Commands
|
|
126
|
+
|
|
127
|
+
```
|
|
128
|
+
xlgit help, and whether everything is set up
|
|
129
|
+
xlgit install set up every repository on this computer (once)
|
|
130
|
+
xlgit install --github add pull request comments to this repository
|
|
131
|
+
xlgit install --repo set up only this repository
|
|
132
|
+
xlgit uninstall [--repo] undo the setup
|
|
133
|
+
xlgit diff what changed in your workbooks since the last commit
|
|
134
|
+
xlgit diff FILE ... in one workbook
|
|
135
|
+
xlgit diff OLD NEW compare any two workbooks (--markdown for a table)
|
|
136
|
+
xlgit diff --html [FILES] open the changes in your browser (--out=page.html to save it)
|
|
137
|
+
xlgit scrub FILE... copies with every value made up, safe to attach to a bug report
|
|
138
|
+
xlgit scrub --merge FILE the three versions of a merge that went wrong, scrubbed, in one zip
|
|
139
|
+
xlgit --version
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
Git runs `xlgit textconv` and `xlgit merge` itself; you don't need to.
|
|
143
|
+
|
|
144
|
+
Don't want a package? `xlgit.py` is a single file. Copy it in, `pip install openpyxl lxml`, and run `python xlgit.py install`.
|
|
145
|
+
|
|
146
|
+
### The GitHub Action
|
|
147
|
+
|
|
148
|
+
`xlgit install --github` writes this workflow. You can also add it by hand:
|
|
149
|
+
|
|
150
|
+
```yaml
|
|
151
|
+
# .github/workflows/excel-diff.yml
|
|
152
|
+
name: Excel diff
|
|
153
|
+
on:
|
|
154
|
+
pull_request:
|
|
155
|
+
paths: ["**/*.xlsx", "**/*.xlsm"]
|
|
156
|
+
permissions:
|
|
157
|
+
contents: read
|
|
158
|
+
pull-requests: write
|
|
159
|
+
jobs:
|
|
160
|
+
excel-diff:
|
|
161
|
+
runs-on: ubuntu-latest
|
|
162
|
+
steps:
|
|
163
|
+
- uses: actions/checkout@v4
|
|
164
|
+
with:
|
|
165
|
+
fetch-depth: 0
|
|
166
|
+
- uses: MichaelFowler1/excel-git@v0.1.0
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
It keeps one comment per pull request up to date as you push. Pull requests from forks can't be commented on with GitHub's default token, so for those the changed cells go in the run's summary page instead.
|
|
170
|
+
|
|
171
|
+
## Tests
|
|
172
|
+
|
|
173
|
+
```bash
|
|
174
|
+
pip install -r requirements-dev.txt
|
|
175
|
+
python -m pytest tests
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
The tests build workbook versions with charts, comments and named ranges, run real `git merge` through the driver, and check every object survived. Set `XLGIT_KEEP=some/dir` to keep the merged files and open them in Excel yourself.
|
|
179
|
+
|
|
180
|
+
### Fuzzing against real workbooks
|
|
181
|
+
|
|
182
|
+
`fuzz/merge_fuzz.py` runs the merge over a folder of real spreadsheets. For each one it makes two branches with random cell edits, merges them, and checks the result: it opens, every edit from both sides is there with its exact type and value, nothing else changed, no chart, table or pivot was lost, and the conflicts reported are exactly the cells both sides changed.
|
|
183
|
+
|
|
184
|
+
```bash
|
|
185
|
+
pip install py7zr
|
|
186
|
+
fuzz/fetch_corpus.sh corpus # ~17,000 workbooks: Enron corpus + open-source test suites
|
|
187
|
+
python fuzz/merge_fuzz.py corpus --keep failed/
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
`--keep` saves the base, ours, theirs and merged files of every failure. `--rounds N` tries N different random edits per workbook.
|
|
191
|
+
|
|
192
|
+
## Limits
|
|
193
|
+
|
|
194
|
+
- Formatting changes their branch made to existing cells come over only when both branches have the same set of styles. Otherwise your formatting is kept.
|
|
195
|
+
- Column widths, merged cells and conditional formatting on existing sheets aren't merged. Yours are kept.
|
|
196
|
+
- Until Excel refreshes a merged pivot, the numbers in its cells are the old ones. Excel does this on open, but tools that read the file without Excel (pandas, openpyxl) see the stale values.
|
|
197
|
+
- Slicers, timelines and tables linked to external data connections aren't merged. They're reported as conflicts so nothing disappears silently.
|
|
198
|
+
- If both branches added a chart to a sheet that had none, only yours is kept (flagged).
|
|
199
|
+
- Inserted and deleted rows are followed when only one branch changed a sheet's rows. If both branches inserted or deleted rows on the same sheet, that sheet merges cell by cell, and cells that only moved can show up as conflicts.
|
|
200
|
+
- Rows are recognised by their contents. A row change can't always be recognised (say, deleting one of many identical rows); then the sheet merges cell by cell and anything unclear is reported as a conflict, never guessed.
|
|
201
|
+
- Inserted columns show as changed cells for now.
|
|
202
|
+
- `.xls` (the old pre-2007 format) isn't supported. Save as `.xlsx`.
|
|
203
|
+
|
|
204
|
+
## Roadmap
|
|
205
|
+
|
|
206
|
+
Next up: testing against thousands of real workbooks, understanding inserted and moved rows, then code review for spreadsheets: showing what a change does to the numbers, tests that run on every pull request, and a linter. See [ROADMAP.md](ROADMAP.md).
|
|
207
|
+
|
|
208
|
+
## License
|
|
209
|
+
|
|
210
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
|
211
|
+
|
|
212
|
+
You can use, change and ship xlgit, including commercially. If you pass it on, modified or not, keep the [NOTICE](NOTICE) file and the copyright line at the top of `xlgit.py` with it. The license doesn't grant use of the xlgit name for your own version.
|
|
213
|
+
|
|
214
|
+
Created by Michael Fowler.
|
xlgit-0.1.0/README.md
ADDED
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
# xlgit
|
|
2
|
+
|
|
3
|
+
> Beta (0.1.0). Every release is fuzz-tested against thousands of real-world workbooks, but you'll still find cases it gets wrong. Git keeps every version, so a bad merge can always be undone. Please [open an issue](https://github.com/MichaelFowler1/excel-git/issues) when something looks wrong.
|
|
4
|
+
|
|
5
|
+
Git and GitHub treat `.xlsx` as an opaque binary blob. You can commit, fork and branch it, but a diff just says "binary file changed" and any merge where both sides touched the file is a conflict. This fixes that.
|
|
6
|
+
|
|
7
|
+
## What you get
|
|
8
|
+
|
|
9
|
+
| | Without xlgit | With xlgit |
|
|
10
|
+
|---|---|---|
|
|
11
|
+
| `git diff` | `Binary files differ` | `changed Budget!B2 1000 -> 1100`, plus inserted, deleted and moved rows, and chart, image and comment changes |
|
|
12
|
+
| Seeing changes | n/a | `xlgit diff --html`: the sheet as a grid in your browser, changes highlighted |
|
|
13
|
+
| Merge, different cells edited | conflict, pick one whole file | merges cleanly |
|
|
14
|
+
| Merge, one side inserted rows | conflict | the other side's edits follow their rows |
|
|
15
|
+
| Merge, same cell edited | conflict | conflict on just that cell, listed in a `_merge_conflicts` sheet with a link to it |
|
|
16
|
+
| Charts, images, comments, formatting, macros | n/a | kept, and their edits to them carried over |
|
|
17
|
+
| Tables and pivot tables | n/a | merged: you add rows, they add a column, you get both |
|
|
18
|
+
| Pull request on GitHub | "binary file not shown" | bot comment with a table of every changed cell, chart, table and pivot |
|
|
19
|
+
|
|
20
|
+
Formulas are compared as formulas (`=B2+C2`), not their cached results.
|
|
21
|
+
|
|
22
|
+
## Get started
|
|
23
|
+
|
|
24
|
+
You need git. Then, once per computer, either:
|
|
25
|
+
|
|
26
|
+
**With Python** (3.9 or newer):
|
|
27
|
+
|
|
28
|
+
```bash
|
|
29
|
+
pip install xlgit
|
|
30
|
+
xlgit install
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
**Without Python:** download the program for your computer from the [latest release](https://github.com/MichaelFowler1/excel-git/releases/latest) (`xlgit-windows.exe`, `xlgit-macos-arm64` for Apple silicon, `xlgit-macos-intel`, `xlgit-linux`), rename it to `xlgit` (`xlgit.exe` on Windows), put it somewhere it will stay, and run `xlgit install`. On Windows you can also just double-click it and it offers to set itself up. The downloads aren't code-signed yet: on Windows click "More info" then "Run anyway"; on a Mac, right-click it, choose Open, then Open again. If you move the program later, run `xlgit install` again.
|
|
34
|
+
|
|
35
|
+
That's it. Every git repository on this computer now understands `.xlsx` and `.xlsm` files, including ones you clone or create later. Keep using git the way you already do.
|
|
36
|
+
|
|
37
|
+
To also get a comment listing the changed cells on every GitHub pull request, run this once inside the repository and push:
|
|
38
|
+
|
|
39
|
+
```bash
|
|
40
|
+
xlgit install --github
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Run `xlgit` on its own at any time to see the commands and check that everything is set up.
|
|
44
|
+
|
|
45
|
+
## Everyday use
|
|
46
|
+
|
|
47
|
+
**See what changed.** `xlgit diff` lists every cell that changed in your workbooks since the last commit. Inserted, deleted and moved rows show up as rows, not as every cell below them changing. `git diff`, `git log -p` and `git show` show cell changes too.
|
|
48
|
+
|
|
49
|
+
```
|
|
50
|
+
$ xlgit diff
|
|
51
|
+
=== budget.xlsx ===
|
|
52
|
+
changed Budget!C3 350 -> 999
|
|
53
|
+
row inserted Budget row 4 A: 'Gas', B: 60, C: 70, D: =B4+C4
|
|
54
|
+
changed Budget!D8 =SUM(D2:D6) -> =SUM(D2:D7)
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
`xlgit diff --html` opens the same changes in your browser, laid out like the spreadsheet:
|
|
58
|
+
|
|
59
|
+
<img src="docs/visual-diff.png" alt="Visual diff: a changed cell shows its old value, an inserted row is green, a changed SUM range is highlighted" width="480">
|
|
60
|
+
|
|
61
|
+
**Merge.** `git merge` and `git pull` combine edits from both branches cell by cell. If you changed different cells, there's nothing to do.
|
|
62
|
+
|
|
63
|
+
**When both of you changed the same cell**, git stops and xlgit tells you which cells:
|
|
64
|
+
|
|
65
|
+
```
|
|
66
|
+
xlgit merged budget.xlsx: took 3 cell(s) from the other branch, but 1 change(s) clash.
|
|
67
|
+
Budget B2: yours 1100, theirs 1200 (was 1000)
|
|
68
|
+
Your values were kept. Every clash is listed, with a link, on the sheet '_merge_conflicts'.
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
Open the workbook, go through the `_merge_conflicts` sheet (each row links to its cell), fix the cells, delete that sheet, save, then run `git add budget.xlsx` and `git commit`. Git keeps every version, so nothing is ever lost: `git merge --abort` undoes the whole merge.
|
|
72
|
+
|
|
73
|
+
## Found a problem?
|
|
74
|
+
|
|
75
|
+
Please [open an issue](https://github.com/MichaelFowler1/excel-git/issues/new/choose). Bug reports on real workbooks are the most useful thing you can give this project, and you don't have to share your data to do it:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
xlgit scrub --merge budget.xlsx # during a merge that went wrong: base, yours, theirs, in one zip
|
|
79
|
+
xlgit scrub old.xlsx new.xlsx # any workbooks, e.g. for a wrong diff
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
`scrub` makes copies where every number, piece of text, comment, chart label and file property is replaced with made-up values, keeping formulas, layout, charts, tables and pivots, so the problem still shows up. Equal values stay equal across the files scrubbed together. Sheet names and named ranges are kept (formulas refer to them), macros are removed, and images are replaced with blank ones. Open the copies and check them before you share them.
|
|
83
|
+
|
|
84
|
+
## How the merge works
|
|
85
|
+
|
|
86
|
+
A workbook is a zip of XML files: one per sheet, one per chart, one per image and so on. Instead of re-saving the whole thing through a spreadsheet library (which is how charts used to get lost), xlgit starts from your copy's zip and only rewrites the XML that has to change:
|
|
87
|
+
|
|
88
|
+
- **Cells**: 3-way merge cell by cell. A cell only one side changed takes that side's value.
|
|
89
|
+
- **Rows**: if one branch inserted, deleted or moved rows and the other edited cells, the edits land on the rows where their cells ended up, with formula references renumbered the way Excel does it. An edit to a row the other branch deleted is a conflict.
|
|
90
|
+
- **Charts, images, comments, macros**: 3-way merge object by object. If only their branch changed a chart, you get their version. If both did, yours is kept and it's flagged as a conflict. Chart edits caused by cell changes (Excel caches plotted values inside the chart) don't count as edits.
|
|
91
|
+
- **Tables**: merged field by field. Their new column plus your new rows gives a table with both. Tables added on their branch come over, and if both branches added a `Table2`, theirs is renamed `Table3` and their formulas are updated to match.
|
|
92
|
+
- **Pivot tables**: a pivot, its data cache and the cached records merge as one bundle. A branch that only refreshed a pivot (same layout, new data) doesn't count as an edit, so your data change and their "switch to Average" merge cleanly. New pivots from their branch come over, sharing an existing cache when they used one.
|
|
93
|
+
- **Sheets**: new sheets on their branch come over whole, with their charts, tables and pivots. Renames and deletes merge too.
|
|
94
|
+
- **Named ranges**: merged by name.
|
|
95
|
+
|
|
96
|
+
Writers renumber a workbook's internal files on every save (add a chart to an early sheet and every later `chart1.xml` becomes `chart2.xml`). xlgit matches objects by what they are, like "the chart called Chart 1 on sheet Notes" or "table id 3", not by file name, so renumbering doesn't cause false conflicts.
|
|
97
|
+
|
|
98
|
+
Excel recalculates every formula and refreshes affected pivot tables when it opens the merged file.
|
|
99
|
+
|
|
100
|
+
## Commands
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
xlgit help, and whether everything is set up
|
|
104
|
+
xlgit install set up every repository on this computer (once)
|
|
105
|
+
xlgit install --github add pull request comments to this repository
|
|
106
|
+
xlgit install --repo set up only this repository
|
|
107
|
+
xlgit uninstall [--repo] undo the setup
|
|
108
|
+
xlgit diff what changed in your workbooks since the last commit
|
|
109
|
+
xlgit diff FILE ... in one workbook
|
|
110
|
+
xlgit diff OLD NEW compare any two workbooks (--markdown for a table)
|
|
111
|
+
xlgit diff --html [FILES] open the changes in your browser (--out=page.html to save it)
|
|
112
|
+
xlgit scrub FILE... copies with every value made up, safe to attach to a bug report
|
|
113
|
+
xlgit scrub --merge FILE the three versions of a merge that went wrong, scrubbed, in one zip
|
|
114
|
+
xlgit --version
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Git runs `xlgit textconv` and `xlgit merge` itself; you don't need to.
|
|
118
|
+
|
|
119
|
+
Don't want a package? `xlgit.py` is a single file. Copy it in, `pip install openpyxl lxml`, and run `python xlgit.py install`.
|
|
120
|
+
|
|
121
|
+
### The GitHub Action
|
|
122
|
+
|
|
123
|
+
`xlgit install --github` writes this workflow. You can also add it by hand:
|
|
124
|
+
|
|
125
|
+
```yaml
|
|
126
|
+
# .github/workflows/excel-diff.yml
|
|
127
|
+
name: Excel diff
|
|
128
|
+
on:
|
|
129
|
+
pull_request:
|
|
130
|
+
paths: ["**/*.xlsx", "**/*.xlsm"]
|
|
131
|
+
permissions:
|
|
132
|
+
contents: read
|
|
133
|
+
pull-requests: write
|
|
134
|
+
jobs:
|
|
135
|
+
excel-diff:
|
|
136
|
+
runs-on: ubuntu-latest
|
|
137
|
+
steps:
|
|
138
|
+
- uses: actions/checkout@v4
|
|
139
|
+
with:
|
|
140
|
+
fetch-depth: 0
|
|
141
|
+
- uses: MichaelFowler1/excel-git@v0.1.0
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
It keeps one comment per pull request up to date as you push. Pull requests from forks can't be commented on with GitHub's default token, so for those the changed cells go in the run's summary page instead.
|
|
145
|
+
|
|
146
|
+
## Tests
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
pip install -r requirements-dev.txt
|
|
150
|
+
python -m pytest tests
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The tests build workbook versions with charts, comments and named ranges, run real `git merge` through the driver, and check every object survived. Set `XLGIT_KEEP=some/dir` to keep the merged files and open them in Excel yourself.
|
|
154
|
+
|
|
155
|
+
### Fuzzing against real workbooks
|
|
156
|
+
|
|
157
|
+
`fuzz/merge_fuzz.py` runs the merge over a folder of real spreadsheets. For each one it makes two branches with random cell edits, merges them, and checks the result: it opens, every edit from both sides is there with its exact type and value, nothing else changed, no chart, table or pivot was lost, and the conflicts reported are exactly the cells both sides changed.
|
|
158
|
+
|
|
159
|
+
```bash
|
|
160
|
+
pip install py7zr
|
|
161
|
+
fuzz/fetch_corpus.sh corpus # ~17,000 workbooks: Enron corpus + open-source test suites
|
|
162
|
+
python fuzz/merge_fuzz.py corpus --keep failed/
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
`--keep` saves the base, ours, theirs and merged files of every failure. `--rounds N` tries N different random edits per workbook.
|
|
166
|
+
|
|
167
|
+
## Limits
|
|
168
|
+
|
|
169
|
+
- Formatting changes their branch made to existing cells come over only when both branches have the same set of styles. Otherwise your formatting is kept.
|
|
170
|
+
- Column widths, merged cells and conditional formatting on existing sheets aren't merged. Yours are kept.
|
|
171
|
+
- Until Excel refreshes a merged pivot, the numbers in its cells are the old ones. Excel does this on open, but tools that read the file without Excel (pandas, openpyxl) see the stale values.
|
|
172
|
+
- Slicers, timelines and tables linked to external data connections aren't merged. They're reported as conflicts so nothing disappears silently.
|
|
173
|
+
- If both branches added a chart to a sheet that had none, only yours is kept (flagged).
|
|
174
|
+
- Inserted and deleted rows are followed when only one branch changed a sheet's rows. If both branches inserted or deleted rows on the same sheet, that sheet merges cell by cell, and cells that only moved can show up as conflicts.
|
|
175
|
+
- Rows are recognised by their contents. A row change can't always be recognised (say, deleting one of many identical rows); then the sheet merges cell by cell and anything unclear is reported as a conflict, never guessed.
|
|
176
|
+
- Inserted columns show as changed cells for now.
|
|
177
|
+
- `.xls` (the old pre-2007 format) isn't supported. Save as `.xlsx`.
|
|
178
|
+
|
|
179
|
+
## Roadmap
|
|
180
|
+
|
|
181
|
+
Next up: testing against thousands of real workbooks, understanding inserted and moved rows, then code review for spreadsheets: showing what a change does to the numbers, tests that run on every pull request, and a linter. See [ROADMAP.md](ROADMAP.md).
|
|
182
|
+
|
|
183
|
+
## License
|
|
184
|
+
|
|
185
|
+
Apache License 2.0. See [LICENSE](LICENSE).
|
|
186
|
+
|
|
187
|
+
You can use, change and ship xlgit, including commercially. If you pass it on, modified or not, keep the [NOTICE](NOTICE) file and the copyright line at the top of `xlgit.py` with it. The license doesn't grant use of the xlgit name for your own version.
|
|
188
|
+
|
|
189
|
+
Created by Michael Fowler.
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
# Same floor as cost-core: 77.0.3 is the oldest setuptools that reads the
|
|
3
|
+
# SPDX license string plus license-files (PEP 639) below.
|
|
4
|
+
requires = ["setuptools>=77.0.3"]
|
|
5
|
+
build-backend = "setuptools.build_meta"
|
|
6
|
+
|
|
7
|
+
[project]
|
|
8
|
+
name = "xlgit"
|
|
9
|
+
version = "0.1.0"
|
|
10
|
+
description = "Cell-level diff and merge for Excel workbooks in git, keeping charts, tables and pivots"
|
|
11
|
+
readme = "README.md"
|
|
12
|
+
requires-python = ">=3.9"
|
|
13
|
+
dependencies = ["openpyxl>=3.1.0", "lxml>=4.9"]
|
|
14
|
+
# No email: the repository is the contact point. Mirrors CITATION.cff.
|
|
15
|
+
authors = [{ name = "Michael Fowler" }]
|
|
16
|
+
license = "Apache-2.0"
|
|
17
|
+
# NOTICE must travel with every copy, so it ships in the wheel beside LICENSE.
|
|
18
|
+
license-files = ["LICENSE", "NOTICE"]
|
|
19
|
+
keywords = ["excel", "git", "merge", "diff", "xlsx", "spreadsheet", "version-control"]
|
|
20
|
+
classifiers = [
|
|
21
|
+
"Development Status :: 4 - Beta",
|
|
22
|
+
"Environment :: Console",
|
|
23
|
+
"Intended Audience :: Developers",
|
|
24
|
+
"Intended Audience :: Financial and Insurance Industry",
|
|
25
|
+
"Operating System :: OS Independent",
|
|
26
|
+
"Programming Language :: Python :: 3",
|
|
27
|
+
"Topic :: Software Development :: Version Control :: Git",
|
|
28
|
+
"Topic :: Office/Business :: Financial :: Spreadsheet",
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
[project.urls]
|
|
32
|
+
Repository = "https://github.com/MichaelFowler1/excel-git"
|
|
33
|
+
Issues = "https://github.com/MichaelFowler1/excel-git/issues"
|
|
34
|
+
|
|
35
|
+
[project.scripts]
|
|
36
|
+
xlgit = "xlgit:cli"
|
|
37
|
+
|
|
38
|
+
[tool.setuptools]
|
|
39
|
+
py-modules = ["xlgit"]
|
xlgit-0.1.0/setup.cfg
ADDED