Wincent Colaiuta [Tue, 2 Oct 2007 12:35:52 +0000 (14:35 +0200)]
Define macro for "/dev/null"
Reduce the likelihood of typing errors by defining a symbolic macro for
"/dev/null", which has special meaning in Git. If this ever changes in Git
the macro can be modified and gdiff should continue to work.
Wincent Colaiuta [Tue, 2 Oct 2007 11:17:58 +0000 (13:17 +0200)]
Draw top and bottom borders in file source list
This is a preliminary attempt at drawing top and bottom borders in the file
source list. Some visual glitches may be visible near the bottom border if
the window is rapidly resized; the cause of these will need to be
investigated. Also, given that the borders are being drawn just inside the
view it is possible that they could overlap content inside the view; it may
be necessary to find a way to somehow inset the content or instead perform
the drawing elsewhere (the split view itself may be a good candidate for
this as it would localize all top and bottom border drawing to a single
place in the code).
Wincent Colaiuta [Tue, 2 Oct 2007 11:08:22 +0000 (13:08 +0200)]
Fix drawing glitches in source list
The cause of the drawing glitches was a missing super call in the drawRect:
method in the subclass template. Not only are the glitches fixed but the
background color now draws as well.
Wincent Colaiuta [Tue, 2 Oct 2007 10:57:05 +0000 (12:57 +0200)]
Add source list
Add a source list for showing files included in a patch. This is a
(currently NSOutlineView subclass); note that in swapping out NSOutlineView
for the subclass there are serious drawing glitches of the focus ring. A
fairly typical pattern in AppKit (empty subclass behaves radically different
to superclass) but hopefully won't be too painful to troubleshoot.
Wincent Colaiuta [Mon, 1 Oct 2007 22:53:14 +0000 (00:53 +0200)]
Switch to NSTextView and prepopulate with test data
Now WOFileView is an NSTextView subclass rather than just a direct NSView
subclass. This required extensive fiddling in order to get the desired
auto-resizing and wrapping behavior. To show that it works the views are
prepopulated with the sample data.
There are still some rough edges and other things to take care of, namely:
the horizontal scroll bars don't appear until the window size is
manipulated, and; text is drawn aliased (would prefer unaliased text in this
case).
Wincent Colaiuta [Mon, 1 Oct 2007 22:44:00 +0000 (00:44 +0200)]
Change scroll view nesting
Instead of putting the gutter views and file views inside a scroll view put
only the file views inside; this change is apparently necessary because
otherwise I can't get the autoresizing behaviour and auto-showing/hiding of
the horizontal scroll bar to work.
I am not sure whether this is a limitation of the Cocoa text system or there
is some kind of magic incantation that would have enabled it to work the
other way, but without access to the source code it seems doubtful that it
would be worth the effort of trying to figure out what's going on in the
bowels of AppKit.
Fix alignment bug introduced along with the addition of upper and lower
borders. Only the outermost subviews need to be inset by one pixel;
additional subviews nested inside the outermost subviews automatically
inherit that base positioning and therefore shouldn't specify an
additional inset.
Wincent Colaiuta [Mon, 1 Oct 2007 13:46:27 +0000 (15:46 +0200)]
Move WO_BORDER_WIDTH macro into global header
Make WO_BORDER_WIDTH macro globally accessible so that it can be used by
both WOGutterView and WOGlueView. Also clarify Doxygen comments about the
unit of measurement used when specifying dimensions.
Wincent Colaiuta [Mon, 1 Oct 2007 13:39:52 +0000 (15:39 +0200)]
Draw borders in gutter views
Equip WOGutterView with border-drawing code and set up the left gutter view
to draw a border on the right and the right gutter view to draw a border on
the left.
Wincent Colaiuta [Mon, 1 Oct 2007 13:13:29 +0000 (15:13 +0200)]
Move test setup code into windowControllerDidLoadNib
In order to do some additional set-up for the test code, move it into the
windowControllerDidLoadNib: method (which is called after the nib is already
set up and the WODiffView has been initialized).
Wincent Colaiuta [Mon, 1 Oct 2007 10:22:15 +0000 (12:22 +0200)]
Split WOFileView into superclass with two subclasses
WOFileView is now a superclass of two new subclasses, WOFromFileView and
WOToFileView. Almost all of the behaviour will be in the superclass with
minor overrides in the subclasses. Basically the only difference in
behaviour is which part of the represented WOFile object the subclass
represents.
Add placeholder stepper and text to the document window. This will later be
used to step through changes within a file (option-click will allow you to
cross file boundaries if you wish) while showing status text of the form,
"Showing change %d of %d (%s)" (two numbers and a change-type string such as
"insertion", "deletion" or "edit").
WODocument is a controller class which previously set up its own document
window and all the subviews. Now all of the subview initialization has been
moved into WODiffView.
This results in a much simpler controller implementation, and there is
better locality now that all subview management is encapsulated in
WODiffView (previously only the autoresizing code was there).
When the controller needs to access the subviews (for example to initiate
scrolling) I will add the appropriate properties or action methods to the
WODiffView class.
Fix another mistake in the botched history rewrite (see 4679d86 for more
information); this is a debugging log statement that should have been
removed but which was left in.
According to the documentation, programmatically created NSScrollViews have
no scrollers, so remove the redundant calls to the setHasVerticalScroller:
method.
Completing the work started in 390c5b3, the right hand side is now also
located inside a scroll view with no vertical scroll bar. The NSScroller
that was previously grouped along with the left file view and its gutter
has now been move outside of the group (it makes no sense to have a
scroller inside the document view of an NSScrollView).
This required some reworking of the WODiffView autoresizing code, and in
the process I discovered that the incorporation of the "slack" in the
glue view (introduced in commit 50b3dbc) was being thrown away during
window resizes; so as part of the updates to the autoresizing code I now
ensure that the slack is accommodated by the glue view during window
resizing as well.
Previously we used the right view to pick up the one-pixel slack caused by
dividing the window in two when there were on odd number of pixels
available. This was slightly ugly because it introduced an arbitrary
difference between the left and right views and therefore incurred a slight
maintenance penalty.
Now we use the glue view for picking up that slack when necessary. Seeing
as there is only one glue view, it is in the center of the window, and its
purpose is to draw curves rather than proportional characters it is the
ideal candidate for this role.
The default NSView subclass templates include no-op methods that can be
overridden. Seeing as the WODiffView class is unlikely to ever need to
override these methods, remove them and fall through to the super class
implementations.
Rather than replicate all the work done by NSScrollView (and behind the
scenes NSClipView) I am going to try letting NSScrollView do as much as
possible. This commit starts the migration, placing the content on the
left-hand side inside a programmatically created scrollview.
The previously used NSView that was used for grouping purposes is still
required seeing as an NSScrollView expects a single document view. The
autoresizing machinery works without modification using the scrollview
as a drop-in replacement for the NSView.
Add back in a missing header import. This was actually in there before but I
shot myself in the foot trying to amend the last commit (used git-reset when
I should have used git-checkout; all I wanted to do was exclude a file from
the commit that I really wanted to be in a separate, later commit). In
reconstructing the history I missed out the header import. Rather than
rewrite the history again I'll just chalk this up as a lesson learned and do
this as a separate commit.
As proof that commit 6c21acf really works, here we read the sample files in
at launch time upon initializing the document controller. This is temporary
code that will be used for prototyping purposes only.
In setting up the test sample files I inadvertently included a sample file
generated using "git-show COMMIT_ID", thus producing a commit description
including the commit message and other preamble prior to the diff (in
contrast to the "naked" output that you would get from "git-diff").
This commit modifies the state machine to skip over any such preamble prior
to attempting to recognize the "meat" of the diff. This means that gdiff
will be able to operate not only on the bare diff output of "git-diff" but
also on that of "git-show" and potentially other sources as well (patch
emails are a possibility, for example, although they may require some
pre-processing if they are encoded using a transport encoding).
Add some sample files that will be used to scaffold development of the gdiff
view code. These files are expected to be in the ~/tmp directory at runtime.
Replace use of the NSThread detachNewThreadSelector:toTarget:withObject: API
with the new NSObject performSelectorInBackground:withObject: API. This is a
readability improvement which more clearly expresses the intention behind
the creation of the new thread.
This commit adds a Doxygen target for producing code-level documentation.
At the moment, the latest Doxygen release (1.5.3) doesn't build on Leopard
although a pre-built binary does run. Doxygen does not yet understand the
property syntax of Objective-C 2.0.
Likewise, the latest version of Graphviz (2.8) (whose "dot" tool is invoked
by Doxygen to depict inheritance hierarchies) doesn't yet build on Leopard,
although I suspect an existing binary would work without problems.
When bringing up the installation window don't just order it to the front;
actually make it the key window as well. In this way the user can just hit
the Return or Enter keys to initiate the default install.
During forced failure testing discovered another missing early return; when
the installer tool was failure to communicate its process id back to the
parent the main appliction wasn't aborting.
This commit creates a new method that handles the call to AuthorizationFree
and prints a diagnostic message as appropriate. This cleans up some
repetition in the code (AuthorizationFree was previously called in three
places) and enables for cleaner program flow around the call to
AuthorizationExecuteWithPrivileges; specifically, we now handle one error as
a special case (failure to execute the tool), then all other errors (general
authorization failures) and finally the success case.
This commit also fixes a bug wherein the failure cases did not result in the
early termination of the method.
Using the shared support folder rather than the localizable resources folder
should be more secure as it reduces the number of possible sites in which an
attacker could substitute a hostile binary.
As noted elsewhere in the source, these types of security measures are an
unwinnable battle; basically any application that runs with elevated
privileges should be considered vulnerable of other users have write access
to any part of it. Despite this, a "defense in depth" strategy suggests that
plugging these minor holes is still a worthwhile idea, especially when the
effort of doing so is minimal.
The pathForAuxiliaryExecutable: method works for Foundation tools
(Objective-C executables linked against the Objective-C runtime) but not for
pure C executables. So must use pathForResource:ofType: to locate the
installer tool instead.
Although ditto should be present on all Mac OS X systems (it is included in
the base install) NSTask could conceivably throw an exception if the launch
path is not accessible (for example if the user has manually removed ditto);
catch those exceptions.
Add a menu item to bring up the installer dialog, set up the bindings and
connections for initiating installation in response to button clicks, show a
progress indicator and disable the buttons when an install is in action.
This is only the user interface; the back end has not yet been put in place.
Due to shortcomings in Apple's AuthorizationExecuteWithPrivileges API
(namely, the fact that it doesn't return the exit code or the process id of
the executed process) it is necessary to interpose an intermediary wrapper
process that explicitly communicates its process id back to the parent
process prior to executing the real installer.
Hideously cumbersome approach but it's the way the Apple sample code does
it.
Add a (currently blank) application controller to the main menu nib. This will
later be used to manage any application-wide aspects such as preferences (if
any) and installation of the command-line tool.
The refactoring tools provided with Xcode don't actually handle all the places
where renaming a class may require changes to be made. In this case the
Info.plist file's NSDocumentClass key still had a stale value.
The prefix header already covers the majority of system header imports
throughout the code, so remove those unnecessary imports, saving a few
lines in many different files.
For better readability, break out two chunks of the path_for_tool function
and place them in dedicated functions of their own:
get_search_path_from_environment and get_search_path_from_sysctl. This
makes the program flow a little more readable and also enables some neater
handling of resource cleanup, seeing as now only one of the function
explicitly mallocs memory.
Move tool-locating code into a separate function where it can be used by both
the command-line tool (to locate git-diff) and the GUI tool (to locate
git-cat-file).
(2) As a special case, when supplied a single argument, "--help", shows usage
information.
(3) In all other cases tries to invoke git-diff, passing along the supplied
arguments and capturing the input. That is:
gdiff foo bar
Will invoke:
git-diff foo bar
And capture the output.
The git-diff tool is searched for in the locations defined by the PATH
environment variable, and if no PATH is set the search falls back to the
locations specified by the user.cs_path sysctl setting.
At the moment the tool doesn't actually do anything with the input. A future
commit will add the ability to locate the GUI application and pass the input
to it for processing.
Rather than having a tool subdirectory and a gui subdirectory the top-level
directory now corresponds to a single Xcode project which contains two
targets, one for the GUI and one for the command-line tool.
In an effort to fix the spurious rebuilds mentioned in commit 828150d I split
the Ragel generation into two phases, each with a separate build rule.
The first phase operates on ".rl" files, feeding them into ragel to produce
XML files with a ".ragel" extension.
The second phase takes those ".ragel" files and uses rlgen-cd to produce the
Objective-C source.
The generation of dot files was removed entirely to eliminate a potential
source of complication in the dependency analysis (although it should work
with any number of files).
Although the spurious rebuilds persist (curiously only in Release builds, not
in Debug builds) I am going to keep this commit as I think the two phase
approach is more robust. I can later add in dot-file generation as a separate
target if desired.
Migrate the WODiffMachine class to the GUI application build as well using a
custom build rule to generate the Objective-C (.m) file from the Ragel (.rl)
source.
Although this works there appears to be a problem in the Xcode dependency
analysis wherein ragel is run on every single build even when no files have
been changed; this in turn causes the built WODiffMachine Objective-C file
to itself be rebuilt, and the project relinked. Hopefully will be able to
isolate the cause of these unnecessary rebuilds and eliminate them.
Add skeletal methods called whenever the user interacts with the scroller. For
the time being doesn't actually do anything, pending the addition of the model
code.
Introduce the basic view hierarchy, using the MyDocument class as a controller
which instantiates and configures a WODiffView and its subviews.
WODiffView is a simple container view that implements the auto-resizing logic,
ensuring that when the parent window is resized the subviews are appropriately
adjusted to match.
On the left we have an NSView which is used to group together a WOGutterView
(for displaying line numbers) and a WOFileView (for displaying the contents of
a blob). In the middle we have an WOGlueView which is used to draw shaded
curves which visually link together changes made on one side (the "from" file)
and those made on the other (the "to" file). Finally, on the right we have
another NSView which is used to group together another WOFileView, another
WOGutterView and an NSScroller.
When resizing the window all "columns" should remain fixed in width except for
the two WOFileViews, which should grow and shrink as required to fill all of
the available space. All columns resize vertically to fill all available
space.
Ripping out the atoi() calls allows another great simplification to the state
machine: instead of having to maintain multiple pointers to the starting
characters of the desired substrings it is sufficient to have a simple
accumulator variable and use a Ragel "all transitions" action ($) to perform
an incremental ASCII-to-integer conversion.
This not only saves a few lines of code, it should also be faster due to the
saved function calls.
While thinking about how to handle possible different, platform-dependent text
encodings in path names I realized that the best thing to do would be to be
encoding-agnostic and do what Git goes: display the raw bytes of the path
using escape sequences for non-ASCII and non-printable characters.
gdiff doesn't actually need the paths in order to show the file contents (it
will ask Git for the blobs based on their hash identifiers) so the paths are
for display purposes only. For display the non-ambiguous format using escape
markers is actually much better. This simplifies the code greatly and makes it
much more robust.
Update the state machine to capture blob ids and store them in the WOFile
objects. This is a simple ASCII capture which requires a pointer to be set on
seeing the start of the desired substring and then actually creating a new
object based on the subtring upon reaching the end.
Note that these are abbreviated SHA-1 hashes, not full numeric hashes, so I
store them as strings rather than numbers. I will later be passing these
strings to other Git tools (such as git-show) so there is no point in using
a numeric representation.
Given that this requires the addition of the ASCII capture machinery I also
use it for capturing unquoted paths, seeing as it is simpler and faster.
At this point I did some experimentation and discovered that quoted paths and
numeric escapes of the form "\ddd" are used whenever a path contains non-ASCII
characters in it. This commit includes some proof-of-concept code for
recognizing and recording these numeric escapes, although as noted in the
comments to make it actually work I'll need to instead capture raw bytes one
by one (most likely into an NSMutableData object) and finally converting the
data to a string in one shot rather than working at the per-character level as
I currently do.
Updates to WOFile and WODiffMachine to record filenames during scanning. At
this point the WOFile class is basically complete as all it has to do is
track filenames and changes.
Provide basic implementations of the model classes that are used to represent
diffs, and modify the state machine implementation to make use of them. In its
current form gdiff can now produce WODiff objects containing a list of WOFile
objects which themselves contain a list of WOChange objects for the sample
input files (basic diffs); support for special case diffs is still to be
added.
This commit refactors the previous implementation, splitting it off into a
thin objective wrapper with a separate main executable implementation.
In addition the basic models which the state machine will use to build up a
representation of the diff (diffs, files, changes) have been added.
This is a skeletal implementation of a Ragel state machine for parsing the
output of "git diff". At this stage it doesn't actually do anything other than
recognize the diff and print logging statements along the way to show what
input it is seeing.