uncategorized

Fine-grained committing and extending Nautilus

When you want to commit changes with Monticello, you have essentially two ways:

  • Monticello Browser (or Nautilus)
  • Komitter

The problem with Monticello is that it is really an all-or-nothing solution… you just enter the commit description and that’s it. This is fine most of the time, but not always — maybe you want to commit across several packages, or maybe you want to commit just a single method. Not to mention that you don’t actually see what you are committing, unless you open a separate changes diff.

Do I really want to commit everything?

To address this problem, there’s a Komitter tool (World > Tools > Komitter). Unfortunately, this tool has a different problem — it shows you the changes in the entire system, which considering I have a lot of custom system overrides is a lot.

All changes in the system

Of course, I can manually not include everything I don’t want, but that’s a lot of work since I would have to do it every time.

Komitter on a single package

So, let’s find a middle ground. Looking at the Komitter class, I found a suspicious method

Komitter class>>openAndCommitToMonticelloWorkingCopiesFilteredBy: aFilterBlock

This method opens the Komitter just on working copies (=the uncommitted packages) matching aFilterBlock, which means I can do something like this

1
2
3
Komitter openAndCommitToMonticelloWorkingCopiesFilteredBy: [ :workingCopy |
workingCopy package name matches: 'Roassal2'
].
Roassal2 changes with a nice diff

That’s so much better! But who would want to type something like that every time?

Extending Nautilus menus

Nautilus can be extended in many ways, one of which is extending the context menus. I want to add an entry just above the “Changes with…” that would open Komitter on the selected package(s).

context-menu.png

All we have to do is to define somewhere a class-side method containing the <nautilusGlobalPackageMenu> pragma.

For inspiration, we can take a look at the class side of AbstractNautilusUI or NautilusMonticello where we can find the definitions of the currently visible menu.

So, to add new entry, let’s create a new method on the class side of Komitter in *MyKomitter-menu protocol (it doesn’t matter where it is, but I like to keep extensions close to what they operate on).

1
2
3
4
5
6
7
8
9
10
11
12
Komitter class>>packagesMenu: aBuilder
<nautilusGlobalPackageMenu>
| packageNames target |
target := aBuilder model.
(packageNames := target selectedPackages collect: #name) ifEmpty: [ ^ target ].
(aBuilder item: #'Commit with Komitter...')
action: [
Komitter openAndCommitToMonticelloWorkingCopiesFilteredBy: [ :workingCopy |
packageNames includes: workingCopy package name ] ];
order: 1295;
help: 'Open a Komitter on the selected package(s)';
icon: Komitter taskbarIcon

The target in the above script is NautilusUI instance (you can always throw in self halt to explore). The order has been taken from NautilusMonticello, so it’s just where I wanted it to be.

komitter-menu.png

And we are done!

Adding it to Startup Scripts

Finally, since I wouldn’t want to add it manually every time, I’ve made it into a startup script — simply compile the method into the class on first startup (don’t forget to save the file wherever your Pharo’s startup scripts are stored). (There’s a small bugfix included for Pharo 5.)

startup scriptkomitter-nautilus-menu.st
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
StartupPreferencesLoader default executeAtomicItems: {
StartupAction
name: 'Add entry to Nautilus package menu to use Komitter'
code: [
|remotesCode code|
"Workaround for Pharo 5 https://pharo.fogbugz.com/f/cases/18927/Komitter-throws-up-if-it-has-committed-everything"
remotesCode := 'remotes
| result |
self packages ifEmpty: [ ^ #() ].
result := self packages first remotes.
self packages allButFirst do: [ :each |
result := result intersection: each remotes ].
^ result collect: [ :each | each koRemote ]'.
KomitStagingArea compile: remotesCode.
code := 'packagesMenu: aBuilder
<nautilusGlobalPackageMenu>
| packageNames target |
target := aBuilder model.
(packageNames := target selectedPackages collect: #name) ifEmpty: [ ^ target ].
(aBuilder item: #''Commit with Komitter...'')
action: [
Komitter openAndCommitToMonticelloWorkingCopiesFilteredBy: [ :workingCopy |
packageNames includes: workingCopy package name ] ];
order: 1295;
help: ''Open a Komitter on the selected package(s)'';
icon: Komitter taskbarIcon'.
Komitter class compile: code classified: '*MyKomitter-menu'.
]
runOnce: true.
}