Archive for the 'Version control' Category

Git fetch weirdness explained

Sunday, August 30th, 2009

In another tussle with Git, I performed the following sequence of commands:

  1. cd mach_star-rentzsch
  2. git fetch (from GitHub)
  3. cd ../mach_star-boredzo
  4. git fetch rentzsch (in this context, my git-remote alias for ../mach_star-rentzsch)
  5. git merge rentzsch/master

Step 5 failed with “Already up-to-date”.

What? I just fetched! I should have new commits to merge in!

Nope. For one thing, this output from step 4:

From ../mach_star-rentzsch
 * [new branch]      master     -> rentzsch/master

seems to mean “OK, here’s a new name for the master branch of that other repository, but we didn’t actually bring in any commits”.

The reason it didn’t bring in any commits is because git fetch apparently only fetches commits that are ancestors of the source repository’s current HEAD. In English: git fetch cares what you have checked out in the source repository.

It’s because I had fetched in step 2, and thereby not changed my HEAD, that step 4 did not see anything new to fetch. I don’t know why it works that way, or why they consider it useful.

Anyway, the “correct” sequence of steps is not much different: git pull, not fetch, in step 2 above. Or use Mercurial, which I’ve found makes a lot more sense in general.

It’s entirely possible that I’ve figured this out the wrong way, so take this entire explanation with a grain of salt.

How to make hg merge, hg resolve use FileMerge

Wednesday, April 22nd, 2009

Put this in your ~/.hgrc file:

[merge-tools]
filemerge.executable=opendiff
filemerge.args=$other $local -ancestor $base -merge $output
filemerge.gui=True

(Based on the original description of and Matt Mackall’s comment on a Mercurial bug about merging.)

hg precommit hooks and the Clang Static Analyzer

Friday, April 3rd, 2009

Fraser Speirs has a post about configuring your Git repository to vet commits with the Clang Static Analyzer.

The idea of pre-commit hooks is that you get to run a script before the commit happens. Depending on the result code of the script, the commit will either proceed or be aborted.

I wrote a wrapper around the scan-build tool, so that I could run the analyzer by hand with my preferred options at any time: …

The –status-bugs flag is the trick here: it makes scan-build return a non-zero status code if it detects bugs. That’s what we want with Git pre-commit hooks: a non-zero status indicates a possible bug, and that causes the Git commit to be aborted.

Mercurial, of course, has the same feature. The hg book has instructions; I’ll show you how I set up my repository to do this.

First, I created a shell script named RunClang.zsh:

#!/bin/zsh -f
~/bin/checker-latest/scan-build \
    -checker-cfref -warn-objc-methodsigs -warn-objc-missing-dealloc -warn-objc-unused-ivars \
    --status-bugs -o checker.out \
    xcodebuild -configuration $1

Next, I added my precommit hook to the repository’s .hg/hgrc file:

[hooks]
precommit.RunClang = ~/bin/RunClang.zsh Development

Here’s what an example session looks like:

% echo 'Testing precommit testing hook' >> test.txt
% hg ci -m 'Testing precommit testing hook'
[churn churn churn]
** BUILD SUCCEEDED **
scan-build: 17 bugs found.
scan-build: Run 'scan-view [snip]/growl-boredzo-precommit-test/checker.out/2009-04-03-2' to examine bug reports.
abort: precommit.RunClang hook exited with status 1

(255)% hg log --limit=1
changeset:   4188:b208862a586d
tag:         tip
user:        Peter Hosey
date:        Fri Mar 13 05:40:09 2009 -0700
summary:     Fix encoding of the Norwegian Growl-WithInstaller strings file.

17 bugs—mostly leaks. Glad I didn’t commit this test file!

So now you know how to have Mercurial block you from committing if the clang checker can find bugs. This should also work for your unit tests. And if you have 100% test coverage (lucky!), you can combine them: have scan-build build your test-bundle target. Then, the hook will prevent the commit if the checker can find bugs or any tests fail.

I don’t think I’ll actually use this set-up, though.

First, in order to find bugs, you need to build your entire main product. Any significantly large program is going to take a long time to build and analyze—Growl, for example, takes about one-and-a-quarter minutes for a clean build. Even committing to a Subversion repository over dial-up was quicker.

More significantly, precommit hooks like this interfere with patch queues. The mq extension implements patches as mutable commits, so any qnew or qrefresh will run the hook. It would be useful on qfinish, but it’s just annoying on qnew and especially qrefresh, as the all-too-frequent builds thwart rapid iteration.

So, if you use patch queues, this won’t work for you. But, if you don’t, then this should work as well in Mercurial as it does in Git.

Adding Growl support to Mercurial

Wednesday, April 1st, 2009

Add this to your ~/.hgrc file:

[hooks]
changegroup.growl = ((echo "$HG_URL" | grep -vF 'file:' > /dev/null) && growlnotify -n Mercurial 'Pull successful' -m "Pulled at least $(hg log --template='\n' -r$HG_NODE:tip | wc -l | sed -e 's/ //g') changesets from $HG_URL") || true
outgoing.growl = (test "x$HG_URL" '!=' 'x' && growlnotify -n Mercurial 'Push successful' -m "Pushed to $HG_URL") || true

It’s not perfect: Both notifications share the same notification name. As long as that isn’t a problem, this works fine.

New Mercurial extension: Bitbucket Extension

Monday, September 29th, 2008

If you’ve ever used Bazaar, you know that one of its features is a shorthand URL scheme for referring to Bazaar repositories on Launchpad. For example, to clone Sparkle’s main repository:

% bzr clone lp:sparkle

I’ve created an extension that enables you to do the same thing in Mercurial with Bitbucket repositories.

% hg clone bb://boredzo/bitbucketextension

The Bitbucket extension adds two URL schemes: bb:, and bb+ssh:. The bb: scheme communicates with Bitbucket over HTTPS; you can guess what bb+ssh does.

(Note: As of Mercurial 1.0.2, you must include the double-slash, because hg pull will interpret the URL as a relative path without it.)

svn merging is full of fail

Thursday, August 7th, 2008

[SCENE: adium/branches/summer_of_code_2008/unit_testing. I’m merging changes from trunk to this branch.]

svn --version
svn, version 1.5.0 (r31699)
   compiled Jul  5 2008, 04:29:25

Copyright (C) 2000-2008 CollabNet.
Subversion is open source software, see http://subversion.tigris.org/
This product includes software developed by CollabNet (http://www.Collab.Net/).

The following repository access (RA) modules are available:

* ra_neon : Module for accessing a repository via WebDAV protocol using Neon.
  - handles 'http' scheme
  - handles 'https' scheme
* ra_svn : Module for accessing a repository using the svn network protocol.
  - with Cyrus SASL authentication
  - handles 'svn' scheme
* ra_local : Module for accessing a repository on local disk.
  - handles 'file' scheme
* ra_serf : Module for accessing a repository via WebDAV protocol using serf.
  - handles 'http' scheme
  - handles 'https' scheme

___
svn up
At revision 24694.
___
svn st
?      Docs
?      DoxyCleaned
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Incoming/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/outgoing_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/Outgoing/buddy_icon.png
~      Resources/Message Styles/renkooNaked.AdiumMessageStyle/Contents/Resources/incoming_icon.png

(~ = “versioned item obstructed by some item of a different kind”)

rm -Rf Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
___
svn up Resources/Message\ Styles/renkooNaked.AdiumMessageStyle
[snip]
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing 
--- Merging r23937 through r24694 into 'branches/summer_of_code_2008/unit_testing':
[snip]
Conflict discovered in 'branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist'.
Select: (p) postpone, (df) diff-full, (e) edit,
        (h) help for more options: e
[I correct the plist to match what trunk has.]
Select: (p) postpone, (df) diff-full, (e) edit, (r) resolved,
        (h) help for more options: r
___
plutil -lint branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist 
branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj: file does not exist or is not readable or is not a regular file
___
plutil -lint branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj 
branches/summer_of_code_2008/unit_testing/Adium.xcodeproj/project.pbxproj: OK
___
plutil -lint branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities\ Framework/AIUtilities.framework.xcodeproj/project.pbxproj
branches/summer_of_code_2008/unit_testing/Frameworks/AIUtilities Framework/AIUtilities.framework.xcodeproj/project.pbxproj: OK
___
cd branches/summer_of_code_2008/unit_testing   %~/Projects/@otherpeoplesprojects/adium(0)
___
xcodebuild -configuration Development       
[snip]
** BUILD SUCCEEDED **
___
svn ci
Sending        unit_testing
[snip]
Transmitting file data .......................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit failed (details follow):
svn: Checksum mismatch for '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/.svn/text-base/Emoticons.plist.svn-base'; expected: '109d361ce47a49a66e53e98412251398', actual: 'a21c8336d1f839b9aaa8a2795a7e25f5'
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.2.tmp'
___
cd Resources/Emoticons
___
rm -Rf MSN.AdiumEmoticonset 
___
svn up MSN.AdiumEmoticonset
A    MSN.AdiumEmoticonset
[snip]
Updated to revision 24694.
___
cd ../.. 
___
cd ../../..
___
svn merge -r23936:24694 trunk branches/summer_of_code_2008/unit_testing
svn: Working copy path 'Frameworks/Adium Framework/Source/AISharedAdium.h' does not exist in repository
___
svn merge -r23936:24694 trunk/Resources/Emoticons branches/summer_of_code_2008/unit_testing/Resources/Emoticons 
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset 
___
svn merge -r23936:24694 trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset 
___
svn pl Emoticons.plist 
___
svn st
___
popd
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist 
--- trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist       2008-06-26 20:50:51.000000000 -0700
+++ branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist  2008-08-07 11:20:27.000000000 -0700
@@ -367,7 +367,7 @@
                        Name
                        Cool
                
-               I don't know.gif
+               I Don't Know.gif
                
                        Equivalents
                        
[Um, OK. What happened to all the differences it had from trunk that I had merged over? Suddenly, almost all the differences are gone, and the files are nearly the same!]
___
diff -u trunk/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist branches/summer_of_code_2008/unit_testing/Resources/Emoticons/MSN.AdiumEmoticonset/Emoticons.plist
___
cd branches/summer_of_code_2008                %~/Projects/@otherpeoplesprojects/adium(0)
___
svn ci unit_testing 
Sending        unit_testing
Transmitting file data ................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................svn: Commit succeeded, but other errors follow:
svn: Error bumping revisions post-commit (details follow):
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Directory '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/unit_testing/Frameworks/Sparkle.framework/Versions/A/Resources/ja.lproj/SUUpdateAlert.nib' is missing
svn: Your commit message was left in a temporary file:
svn:    '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008/svn-commit.3.tmp'
___
svn st            %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
?      svn-commit.2.tmp
?      svn-commit.tmp
?      svn-commit.3.tmp
 ML    unit_testing
?      unit_testing/Docs
?      unit_testing/DoxyCleaned
  L    unit_testing/Release
  L    unit_testing/Release/openUp
  L    unit_testing/Release/Artwork
M      unit_testing/Release/Makefile
  L    unit_testing/Frameworks
  L    unit_testing/Frameworks/libgmodule.framework
  L    unit_testing/Frameworks/libgmodule.framework/Versions
[snip]

(L in third column = Locked)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
svn cleanup       %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(1)
svn: In directory 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Error processing command 'committed' in 'unit_testing/Frameworks/JSON Framework/Tests/json.org'
svn: Working copy 'unit_testing/Frameworks/JSON Framework/Tests' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
[So I get to manually clear the locks. Yay!]
___
tar cjf unit_testing.tbz unit_testing 
___
cd unit_testing   %~/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008(0)
___
svn st | grep -Fw L | head
  L    Frameworks
  L    Frameworks/JSON Framework
  L    Frameworks/JSON Framework/Tests
A L+   Frameworks/JSON Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/types
A L+   Frameworks/JSON Framework/Tests/format
A L+   Frameworks/JSON Framework/Tests/rfc4627
  L    Frameworks/JSON Framework/Tests/Examples
  L    Frameworks/JSON Framework/Tests/Examples/JSONChecker
A L+   Frameworks/JSON Framework/Tests/jsonchecker
svn: Write error: Broken pipe
___
ls Frameworks/.svn 
dir-prop-base format          prop-base/      text-base/
entries         lock            props/          tmp/
___
ls Frameworks/.svn/lock 
Frameworks/.svn/lock
___
rm Frameworks/.svn/lock 
___
svn st -N Frameworks 
  L    Frameworks/JSON Framework
  L    Frameworks/libintl.framework
  L    Frameworks/libpurple.framework
  L    Frameworks/libmeanwhile.framework
  L    Frameworks/LMX.framework
R L+   Frameworks/Sparkle.framework
  L    Frameworks/AutoHyperlinks Framework
  L    Frameworks/libgobject.framework
  L    Frameworks/OTR.framework
  L    Frameworks/libglib.framework
  L    Frameworks/ShortcutRecorder
  L    Frameworks/libgthread.framework
  L    Frameworks/AIUtilities Framework
  L    Frameworks/OCMock.framework
  L    Frameworks/PSMTabBarControl.framework
  L    Frameworks/RBSplitView
  L    Frameworks/Growl-WithInstaller.framework
  L    Frameworks/Adium Framework
___
svn st -N Frameworks/JSON\ Framework 
  L    Frameworks/JSON Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
rm Frameworks/JSON\ Framework/.svn/lock 
___
svn st -N Frameworks/JSON\ Framework
A  +   Frameworks/JSON Framework/dmg.sh
  L    Frameworks/JSON Framework/Tests
  L    Frameworks/JSON Framework/Site
  L    Frameworks/JSON Framework/JSON.xcodeproj
  L    Frameworks/JSON Framework/Docs
A  +   Frameworks/JSON Framework/bench.m
M      Frameworks/JSON Framework/JSON-Info.plist
M      Frameworks/JSON Framework/CREDITS
___
find . -name lock -print0 | xargs -0 rm
___
svn ci 
svn: Working copy '/Volumes/Home-etc/Users/prh/Projects/@otherpeoplesprojects/adium/branches/summer_of_code_2008' locked
svn: run 'svn cleanup' to remove locks (type 'svn help cleanup' for details)
___
rm ../.svn/lock 
___
svn ci
Sending        unit_testing
[snip]
svn: Commit failed (details follow):
svn: Directory '/branches/summer_of_code_2008/unit_testing' is out of date
___
svn up ..
svn: Failed to add file '../unit_testing/Frameworks/JSON Framework/dmg.sh': a file of the same name is already scheduled for addition with history
___
rm Frameworks/JSON\ Framework/dmg.sh 
___
svn up
D    Frameworks/libgmodule.framework/Versions/2.0.0/Resources/English.lproj
G    Frameworks/JSON Framework/Tests/Types.m
svn: Failed to add directory 'Frameworks/JSON Framework/Tests/json.org': a versioned directory of the same name already exists
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A L+   Frameworks/JSON Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org/1.json
A  +   Frameworks/JSON Framework/Tests/json.org/2.json
A  +   Frameworks/JSON Framework/Tests/json.org/1.plist
⋮
___
find . -name lock -print0 | xargs -0 rm
___
svn st Frameworks/JSON\ Framework/Tests/json.org
A  +   Frameworks/JSON Framework/Tests/json.org
⋮
___
cd ..
___
mv unit_testing unit_testing-fail
___
svn up unit_testing
A    unit_testing

With this fresh working copy, I’m able to build, so I believe I’m finally done.

New tool: sednames

Friday, June 20th, 2008

What if you could use sed to rename files?

Well, now you can.

sednames is a utility that lets you specify a program for sed on the command-line, which it then uses to rename the files that you also specify on the command-line.

The twist is that, unlike other batch-renamers, sednames also supports your VCS. For the most common cases (svn, hg, bzr, and git), there’s a –vcs option:

% sednames -e 's/Replace/Mix/' --vcs=hg *

This command-line will use Mercurial (hg mv) to rename every file by replacing “Replace” with “Mix”. Of course, if a name doesn’t contain “Replace”, then that name will be unchanged and sednames is smart enough to not try to rename that file.

Not only that, but just in case you’re using some oddball VCS (or you want to copy instead of rename, or something), sednames supports using any program to rename your files, as long as it accepts both the before and after names in its arguments. The –help output is more specific, but to put it simply, it works similarly to find‘s -exec option.

You can download the current revision directly, or use the Mercurial repository to follow or contribute to its development.

.hgignore for Mac OS X applications

Thursday, March 20th, 2008

If you use version control (and you should), then you’re familiar with the pollution that an Xcode build folder can put into your status output:

? build/.DS_Store
? build/Debug/UTI Plist Helper.app/Contents/Info.plist
? build/Debug/UTI Plist Helper.app/Contents/MacOS/UTI Plist Helper
? build/Debug/UTI Plist Helper.app/Contents/PkgInfo
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/InfoPlist.strings
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/classes.nib
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/info.nib
? build/Debug/UTI Plist Helper.app/Contents/Resources/English.lproj/MainMenu.nib/keyedobjects.nib
⋮

Good version-control systems offer a way to ignore any file that matches a certain pattern. In the case of an Xcode project, you want to ignore the build folder and a few other things: .DS_Store files, backup nibs (those Foo~.nib packages that IB creates when you save), etc.

In Mercurial, the way to do that is to create a .hgignore file, and populate it with the patterns you want hg to ignore.

In order to save you repetitive work, here’s a .hgignore file, already fully-populated, that you can use when versioning your Xcode-based project with Mercurial:

File: hgignore.bz2

syntax: glob
build
*.swp

*.mode1
*.mode1v3
*.mode2
*.mode2v3
*.pbxuser
*.perspective
*.perspectivev3
xcuserdata

*~.nib

.DS_Store

What to do with this file

  1. Download it, and save the .bz2 file somewhere such as your Documents folder.
  2. cd into the top level of a repository.
  3. Extract the file using this command line: bunzip2 < ~/Documents/hgignore.bz2 > .hgignore
  4. Add the file: hg add .hgignore
  5. Commit it.

Thereafter, not only do you have a .hgignore file keeping your status output clean, but it’s versioned, so it’s easy for you to track and revert changes to the ignore file over time.

UPDATE 2011-05-05: Updated for Xcode 4.

hg st says modified, but hg diff doesn’t say anything

Tuesday, March 11th, 2008

You have a puzzle. When you run hg st, it says one of your files is modified:

hg st                                                              %~/Python/run_tests(0)
M test.py

But when you run hg diff, it doesn’t say anything about the file:

hg diff test.py                                                    %~/Python/run_tests(0)
___
                                                                   %~/Python/run_tests(0)

The reason, at least in my case, was that the file’s permissions had changed. hg st acknowledges this change exactly the same way it acknowledges a change to the file’s contents, but hg diff only shows changes to the contents of the file, not the metadata. Thus, a metadata change only gets reported by hg st and not by hg diff.

While I prefer hg over svn, this is one advantage that svn has over hg. hg only uses one column to indicate the type of change, and shows the same letter (M) for metadata changes as for content changes. svn, on the other hand, uses seven columns, and a metadata change puts the M in a different column.

There’s no way to make hg st use the svn st format, but you can make hg diff show metadata changes. The way to do this is to edit your hgrc file and enable git mode:

[diff]
git=True

There are two hgrc files; you can choose to change either or both. You can edit ~/.hgrc (this is what I recommend), or you can edit the per-repository .hg/hgrc file. (There is no .hg/hgrc file by default, so if you haven’t created one already, you would need to create it.)

The difference, as you’ve probably guessed, is that ~/.hgrc sets hg’s default for all repositories, whereas the per-repository hgrc changes the setting only for one repository, overriding ~/.hgrc and hg’s own defaults.

Once you make this change, the output from hg diff will include metadata information:

hg diff test.py                                                    %~/Python/run_tests(0)
diff --git a/test.py b/test.py
old mode 100755
new mode 100644
___
chmod a+x test.py                                                  %~/Python/run_tests(0)

Quickie: Finding an svn conflict marker with vim

Tuesday, February 12th, 2008

/[<=>]\{7\}

Generating a tarball of your project

Friday, February 8th, 2008

Every good version-control system has this as a built-in feature.

In the below, $TMP is a staging directory, such as “build”, “/tmp”, or “/Volumes/RAM Disk”. It’s also where the final archive will end up.

  • svn: Sort of. svn export $TMP/MyProject && cd $TMP && tar cjf MyProject.tbz MyProject
  • darcs: darcs dist (outputs a .tar.gz file in the current working directory)
  • bzr: bzr export --format=tbz2 $TMP/MyProject.tbz
  • git: git archive --format=tar master | bzip2 > $TMP/MyProject.tbz
  • hg: hg archive -p MyProject -t tbz2 $TMP/MyProject.tbz

More Darcs win: Selective recording

Wednesday, December 12th, 2007

Darcs lets you choose which changes you want to include in a patch (commit). For example:

darcs rec                                                   %~/ASL test apps/aslsearch(0)
move ./aslsearch.m ./aslsearch.c
Shall I record this change? (1/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 1
-#import <Foundation/Foundation.h>
-
-#include <stdlib.h>
-#include <stdbool.h>
-#include <stdio.h>
-#include <unistd.h>
-#include <string.h>
-#include <vis.h>
-#include <asl.h>
-
Shall I record this change? (2/?)  [ynWsfqadjkc], or ? for help: n
hunk ./aslsearch.c 29
-       NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
-
Shall I record this change? (3/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 75
-       [pool release];
Shall I record this change? (4/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch.c 194
+       fprintf(stderr, "key: \e2\80\9c%s\e2\80\9d\noperator: 0x%x\nvalue: \e2\80\9c%s\e2\80\9d\n", key, operator, value);
Shall I record this change? (5/?)  [ynWsfqadjkc], or ? for help: n
hunk ./aslsearch.xcodeproj/project.pbxproj 10
-               8DD76F9A0486AA7600D96B5E /* aslsearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.m */; settings = {ATTRIBUTES = (); }; };
+               8DD76F9A0486AA7600D96B5E /* aslsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.c */; settings = {ATTRIBUTES = (); }; };
Shall I record this change? (6/?)  [ynWsfqadjkc], or ? for help: ?
How to use record...
y: record this patch
n: don't record it
w: wait and decide later, defaulting to no

s: don't record the rest of the changes to this file
f: record the rest of the changes to this file

d: record selected patches, skipping all the remaining patches
a: record all the remaining patches
q: cancel record

j: skip to next patch
k: back up to previous patch
c: calculate number of patches
h or ?: show this help

<Space>: accept the current default (which is capitalized)
hunk ./aslsearch.xcodeproj/project.pbxproj 10
-               8DD76F9A0486AA7600D96B5E /* aslsearch.m in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.m */; settings = {ATTRIBUTES = (); }; };
+               8DD76F9A0486AA7600D96B5E /* aslsearch.c in Sources */ = {isa = PBXBuildFile; fileRef = 08FB7796FE84155DC02AAC07 /* aslsearch.c */; settings = {ATTRIBUTES = (); }; };
Shall I record this change? (6/?)  [ynWsfqadjkc], or ? for help: f
hunk ./aslsearch_Prefix.pch 5
-#ifdef __OBJC__
-#      import <Foundation/Foundation.h>
-#endif
-
Shall I record this change? (10/?)  [ynWsfqadjkc], or ? for help: y
hunk ./aslsearch_Prefix.pch 8
+#include <unistd.h>
Shall I record this change? (11/?)  [ynWsfqadjkc], or ? for help: n
What is the patch name? Switched source file from Obj-C to pure C
Do you want to add a long comment? [yn]n
Finished recording patch 'Switched source file from Obj-C to pure C'

(Before you mention the UTF-8 sequences that Darcs called out as invalid ASCII: Yes, I know.)

From svn to darcs: Listing unknown files

Sunday, December 2nd, 2007

If you’re an svn user looking at darcs, you may wonder how to find unknown files—that is, files that you’ve created in your working directory, but haven’t yet told your VCS about (with svn add or darcs add). In svn, the command is svn st, but in darcs, the procedure is not obvious.

The darcs command that most seems like what you need is darcs whatsnew, or, as I type it, simply darcs what. Like svn st, this shows you all files that have been added, removed, or modified; it also includes a diff, so not only is it your substitute for svn st, it’s also your substitute for svn diff. But it doesn’t show you unknown files.

Until you use the -l (that’s a lowercase L) option.

darcs what -l changes the output greatly, as it’s in a completely different format: one similar to that of svn st. The format is actually that of darcs what -s (summary mode); the difference is that -l also lists unknown files.

There are some differences between darcs what -l and svn st. Some of these differences are:

  • darcs uses fewer columns than svn: It has one status column, whereas svn has six.
  • darcs uses ‘a’ to indicate an unknown file, whereas svn uses ‘?’.
  • darcs uses ‘R’ to indicate a file that you’ve removed, whereas svn uses ‘D’ to indicate a file that you’ve deleted. (svn supports remove and rm as synonyms for its delete command, but darcs does not support delete nor rm as synonyms for its remove command.)

darcs is becoming my favorite VCS

Friday, October 26th, 2007

Here’s a real-world situation that tells you why I’m falling in love with darcs.

I’m working on a plug-in that logs all notifications posted by Apple Mail to a couple of files. The idea of this plug-in is to enable Mail-bundle developers to develop more reliable plug-ins by listening for notifications rather than posing as Mail’s classes or swizzling its methods.

(As you know, I don’t ordinarily announce what I’m working on before it’s ready; for this post, it’s important to provide context.)

The first revision of the plug-in used NSLog, but I found that this spammed console.log rather heavily. So I changed it to use two separate log files, which I write to using NSFileHandles.

Then I started work on a new feature: datestamps. While working on that, however, I noticed that I had forgotten to close and release my two file handles in -dealloc. Oops.

Now, remember that I had local changes. If this code were versioned in svn, I would have to:

  1. bzip2 the source file (which renames it as a side-effect).
  2. svn up the file in order to restore the pristine copy.
  3. Open it in a separate editor (so as not to lose undo history) and add the closeFile and release messages that I’d forgotten.
  4. Save and exit.
  5. Commit.
  6. Delete the committed file and bunzip2 my original one.
  7. Go through an svn diff and redo the fix in my modified file so I don’t clobber the changes in my next commit.

What a hassle. But darcs has first-class support for just this situation.

All I had to do was darcs amend-record. darcs asked me whether the most recent commit was the one I wanted to amend, then it offered me the choice of which hunks of local changes I wanted to include in the amendment. I said yes to the one with the closeFile and release messages and no to all the others. That’s all I had to do.

Let me summarize that for you: I was able to retroactively fix my previous commit while I had other local changes in the same file, without sweeping up those other changes into the commit.

That’s awesome.

What do different VCSs do when a file changes?

Thursday, October 11th, 2007

Here’s the scenario:

  1. You change a file.
  2. You invoke your-vcs-here commit file.txt (or equivalent).
  3. You write a commit message in the editor.
  4. You suspend the editor with ^z and make another change to a file. (Perhaps you started the commit and a build at the same time, and the build failed because you forgot a comma.)
  5. You resume the editor, and issue :wq or ^x^c to save and exit.

What happens?

Subversion

svn commits the changed version of the file—in other words, the file as it existed when the editor exited.

Monotone

mtn notices that the file changed, tells you “you can’t do that”, and throws away saves the commit message.

More precisely, it says:

mtn: misuse: file 'Test.txt' modified during commit, aborting

darcs

darcs commits the file as it existed before it started the editor. (Presumably, it copies changed files to a staging area rather than simply committing them from the WC.)

Mercurial

hg commits the file as it existed before it started the editor, just as darcs does.


UPDATE 2008-03-05: Jack, in a comment below, corrected me on what Monotone does with the commit message. I have applied this correction above.

UPDATE 2008-03-26: jpc, in a comment below, corrected me on what Mercurial does with the commit message. I thought that that’s what I had written, but I wrote the wrong thing: that it acted like svn, rather than darcs. I apologize for the brainfart, and I have applied this correction above.

Quicksilver + SVN = yum

Friday, August 17th, 2007

Go directly to the plug-ins pane of your Quicksilver Preferences and install the Subversion module. Do not pass Go. Do not check out $200.

Best when combined with the comma trick.

So far, I’ve tried both add and ci, and they work in the obvious way. They even provide a notification (which goes through the Growl notifier for me—that’s another plug-in) with the command’s output.

Handy svn technique

Wednesday, November 29th, 2006

Easy way to create a repo and then immediately make your existing source code (foo) into a checkout:

  1. % svnadmin create foo_repo
  2. % mkdir foo_wc foo_wc/branches foo_wc/tags
  3. % mv foo foo_wc/trunk
  4. % svn co foo_repo foo_wc (Note that svn co requires a URL.)
  5. % cd foo_wc
  6. % svn add *
  7. % svn ci

Yes, that’s right: This doesn’t use svn import at all. svn import, in fact, is useless, since (a) it doesn’t give you a WC you can then operate on (you have to check it out again), and (b) it doesn’t let you do multiple things with the commit (e.g. create branches and tags directories at the top level of the repo; you would have to do a second commit).

How to apply changes in a framework to a versioned copy of it

Tuesday, November 14th, 2006

In Adium 1.0, we’ll have a number of external frameworks:

So whenever any of these frameworks changes — especially if it fixes a bug that we’ve been suffering from — we build the framework in our copy of their source code, and then we drop the new built framework into our source repo and commit it.

But I often see it done this way:

  1. svn rm Foo.framework
  2. svn ci Foo.framework
  3. cp -R ../../Foo/build/Release/Foo.framework Foo.framework
  4. svn add Foo.framework
  5. svn ci Foo.framework

This is wrong, for three reasons:

  • Commit history for the framework is lost. svn doesn’t relate the new framework to the old framework that previously occupied the same path.
  • Much space and bandwidth is wasted as all of the files in the framework (including nibs, images, plists) are copied yet again, even those that were not changed.
  • It takes two commits to do one job.

Here’s a better way.

  1. pushd ../../Foo/build/Release
  2. tar cf Foo-framework.tar Foo.framework
  3. mv Foo-framework.tar $OLDPWD
  4. popd
  5. tar xf Foo-framework.tar && rm Foo-framework.tar
  6. svn ci Foo.framework

tar will overwrite every file in the framework, and of course any new ones will be created. A quick svn st will show you which files are new (unversioned) or modified; if there are any unversioned files, you should of course svn add them before committing. And then when you commit, the commit history of all the files is preserved, so you can do svn log (or the equivalent operation in Trac) later, and svn will apply its own smarts about exactly what should be copied.

The only downside to this approach is that any files that no longer exist in the old framework won’t be removed. If you have reason to believe that any files were deleted, you should perform ls -R on both directories, diff -u the two results, and look for “-” lines. But most of the time, this isn’t necessary; how often does anything ever get removed from a framework?

So, anyway, please use tar, for the sake of the commit history. ☺

UPDATE 11:55: If you want an example, here’s Adium changeset number 18243, in which I did this to the LMX framework. See how small that changeset is?

UPDATE 14:08: Fixed SparklePlus link.

How to use svn merge

Sunday, October 29th, 2006

A number of my fellow Adium developers have observed that svn merge is hard to use. I think the main problem is that the command is quite general (it can be put to several different uses) and the documentation not particularly clear. In order to promote better understanding of the svn merge command, here’s what I know about it.

The command in a nutshell: svn merge is essentially svn diff $ARGS | patch -p0; the only real difference is that svn merge will handle addition, movement, and deletion of files, whereas patch won’t.

This now having been stated up front, let’s get into details.

Summary of tasks and how to do them

  1. svn merge sourceURL1[@N] sourceURL2[@M] [WCPATH]
  2. svn merge sourceWCPATH1@N sourceWCPATH2@M [WCPATH]
  3. svn merge [-c M | -r N:M] SOURCE[@REV] [WCPATH]
  • To apply changes from another directory (branch, tag, trunk): Use format #1 (or #2 if you have a checkout of it), supplying at least the first revision (N), and the second (M) if it is not HEAD. With format #1, you should not need to be using the same repository URL as the CWD for either one or both; you should be able to merge between two URLs, or between a single URL into your checkout of a different URL.

  • To travel back in time to a previous revision: Use format #3, with -rHEAD:THEN. For example, -rHEAD:42000.

  • To cherry-pick a changeset (apply only that changeset, not any others): Use format #3, with -cTHEN.

  • To undo a particular changeset without disturbing things that happened after it (hopefully): Use format #3, with -c –THEN. Note the minus sign. For example, to revert r42000, use “svn merge -c -42000 .”. You can also use the -r flag: “svn merge -r 42000:41999 .”. This may be advisable, for clarity reasons, if you have a script that must always revert a particular revision.

    In both examples, note the “.” and the space before it. “.” means the current directory, and is used here as the SOURCE argument to format #3. It’s a separate argument from the revision, so you need the space.

  • All formats apply their changes to the CWD by default. I don’t know what happens if the CWD is not a WC. You can specify a different destination for the changes by specifying a different WCPATH (the last argument in all the formats above).

  • In format #3, the first path that it accepts is a URL or local WC path to the object you want to diff. (Remember my initial statement about svn merge being essentially svn diff | patch -p0.) The second path is a local WC path to the destination for the changes that are observed.

Updated 2007-07-12: Fixed error in traveling back in time (I had the revs backward); added cherry-picking technique.

svn help merge, in RTF

The “svn help merge” text is an invaluable summary of the command’s operation, but it’s plain-text, and therefore somewhat hard to read on a screen. To that end, I created an RTF version and printed it out, and decided to put it up here in case anyone else wants to do the same.