Playing around with the git version control system a bit today. I'm using the GitX GUI right now, which essentially lets you do a bit of merging, committing and viewing the commit log of a local working copy (with pretty branching indicators) and that's it. So, you'll still need the Git Command line Tools for Mac.
Here's a few notes how I'm going about re-training my SVN-primed brain to use git:
- I found a nice Git - SVN Crash course that explains the basic commands. If you use GitX, you still have to do the initial checkout using the command line tool. Also, git has an "svn" command that lets you check out an SVN repository, and even commit back to it, so no special conversion scripts needed.
- At the last CocoaHeads I learned that Git essentially makes the status an explicit area in which stuff goes. So if you switch to Commit View in GitX, you see any modified/new/missing files marked in the lower left list.
To add a new file, mark a modified file for committing, or mark a missing file for removal, just "stage" it. In GitX that means select and right-click and choose "Stage Changes" from the contextual menu. It'll then move into the staging area list in the lower right. So, the "Unstaged Changes" list represents on-disk state, while the "Staged Changes" list represents state that will apply to the next commit.
To commit something, it must have been staged.
One advantage of explicitly staging things is that Git lets you partially stage a file. So if you've made a few changes that are related or "safe", and one change that is completely unrelated or dangerous, you can click the little "stage" buttons next to each line in the diff for an un-staged file to only stage the safe ones, commit that as one commit, then immediately commit the remaining changes separately so you can roll them back more easily. And since you have to push before you commit, your trunk stays consistent and builds just fine, even if your splitting of the two commits accidentally caused a temporarily broken build between those two.
- In Git, the local working copy has an attached repository in its own right. So a commit just writes stuff to the local repository. To actually get it on the server, where your co-workers can get it, you use git push.
That's as far as I've come so far. I'll update this article as I find out more. Anyone wanna share patent recipes and gotchas for common things? Particularly interested in:
Do you always stage/commit/push in 3 steps when you work in teams, or is there a way to do all of that in one go for changes that I know I want my co-workers to see?
- If I rename a file in my working copy without using git (i.e. I just change its name in Finder), how can I get git to notice that I renamed that file? Also, does this also work when the file has local modifications? I tried just staging in GitX, but that made all the changes in the renamed file show up as written by me in the blame, instead of by the original author, so git obviously thought this was a delete/add, not a commit.
The only thing that worked was renaming that file back and then renaming it again in git. Not really elegant, nor practical for a large refactoring.
- If I do my own branch of, e.g. Sparkle, how would I set up a repository to always push to my own github account, but let me push to the Sparkle repository when I think the changes are ready for general consumption? Ideally without re-typing the URL constantly?
So, after having some problems with a detached head in Git, I'm back to trying to work with it more. I found a slightly improved branch of GitX that does checkouts and other things, but the trouble is it broke multiple-part staging. So, I'm currently switching between the two until I get around to trying to find out what exactly is broken.
I also wanted to get some files into Git from my regular SVN repository, including history. That's fairly easy: GIT has built-in Subversion support. So, to check out a Subversion repository into a local git working copy (which also is a full repository, as we're talking DVCS here), you do the same as if you were checking out from a Git repository, but you add an additional "svn" after each command, as in:
my-mac:WCs me$ git svn clone svn://me:email@example.com/path/to/repo/ tempsvn
This will create a new Git working copy with the contents and history of your Subversion repository /path/to/repo/ on the server svnserver.example.com, in a folder named tempsvn, logging in as user me with password password. Piece of cake. This "git-svn" as it is called is cool, because you can use git locally, but it maintains the connection to your SVN server, so you can push to the Subversion server if you want to.
Now, I thought, it would be great to also have a Git server on my server. It's not that difficult. What you need is a server on which git is installed, and to which you have SSH access. For example, you can use a Mac you have sitting around that is accessible from the outside, for example via DynDNS (you may have to port-map port 22 to this server to allow SSH access). Create a new user account, let's call it "git". The easiest way to do this is to just create one in the "Accounts" System Preferences pane. A regular user account (non-admin) should be fine. Set it up in "Sharing" so "Remote Login" is activated for this account (I recommend being paranoid and turning it on only for this user).
Now you can go and access this user's account using Terminal from another Mac, by typing in something like:
my-mac:WCs me$ ssh firstname.lastname@example.org
(assuming that you went with user name git and your Mac is available under the domain name gitserver.example.com) It will ask for a password, maybe also to accept this server as "safe" because there's no signed certificate. That's OK.
Now you're logged in as that user. Create a folder where you'll put all your repositories, one folder for a repository and initialize the actual git repository structure inside that:
my-server:~ git$ mkdir ~/git
my-server:~ git$ cd ~/git
my-server:git git$ mkdir me.git
my-server:git git$ cd me.git
my-server:me.git git$ git init --bare --shared=group
Note the --bare option, which means Git will not create the actual readable files in there like you'd have them in a real working copy, but rather only the repository stuff it needs to store the history and what's checked in. The --shared option sets the permissions on the repository so several user accounts can access it. Now you have an empty repository named "me" (or just pick any other name instead), which will be accessible from the outside via the URL:
where git/me.git is the relative path from user git's home directory to the repository, and git@ again designates what user name to establish an SSH session as. To make it easy to upload files to that server, a good idea is to do:
my-mac:WCs me$ cd ~/WCs/tempsvn/
my-mac:tempsvn me$ git remote add origin email@example.com:git/me.git
so your working copy knows where to push. Then do the actual push:
my-mac:tempsvn me$ git push origin master
This will upload all the files and all the history we just checked out from your SVN repository to the new Git repository.
If you're on a run-of-the-mill Mac with the standard Git package above, you may get an error message now:
bash: git-receive-pack: command not found
fatal: The remote end hung up unexpectedly
If that's the case, your PATH shell variable probably doesn't contain the path of your Git installation's tools, or only contains it for Terminal logins. To fix that, create a ~/.bashrc file on your server through SSH that contains the line:
which should get rid of the error message and let you commit. And now you have your SVN repository migrated to Git, and you have a git server up and running.
If you want to check out this repository on another Mac, now that you've created it, go to whatever folder you want your repositories in and use the git clone command like so:
my-mac:WCs me$ git clone firstname.lastname@example.org:git/me.git MeWC
where MeWC is the name of the new folder that will be created at this location containing your files (Leave that out and it'll use the repository name, in this case me). Since you are cloning, it will already have the remote origin added, and will know where to push to, so no need to do that again.
Update 2: Elaborated on the advantages of staged commits, struck through the question that came from me not quite seeing the advantages yet.
Setting up a Git Server
Git push-ing woes resolved
|Anthony Mittaz writes:|
If I do my own branch of, e.g. Sparkle, how would I set up a repository to always push to my own github account, but let me push to the Sparkle repository when I think the changes are ready for general consumption? Ideally without re-typing the URL constantly?
-- You can add another remote to your repository that point to the main Sparkle repository, you can then decide to push to this remote when changes should be made on the main Sparkle repository, if not you still push to your personal repository using a standard push (push origin master) compared to (push origin (name given to the main Sparkle repo, when adding it with git remote add mainSparkl GIT_URL)
In the command line, you can do 'git commit -am"message"' to commit all changes, and then push those, which makes it 2 steps instead of 3.
If you want to blame a file that has been renamed or contains code from another file, try git blame -M or git blame -C -M, which turns on rename detection. Renames in Git are done with heuristics rather than explicitly in the commits.
You can add multiple remotes, by defoult the 'origin' remote will be used. So if you have your own sparkle branch as origin, add the official repository with something like 'git remote add sparkle email@example.com:sparkle/sparkle.git', then you can push to that with 'git push sparkle'. If you do 'git push', it'll push to origin, which is your own branch of sparkle.
You can also add something like this in your ~/.gitconfig:
insteadOf = "gh:"
insteadOf = "ghp:"
then you can push to private repositories by using something like 'git push ghp:sparkle/sparkle.git' or pull from other repositories by using something like 'git pull gh:pieter/gitx.git master', which saves you a few characters :)
Some answers to your questions above - hopefully they're helpful.
* I usually do things in the 3 stages you mention in question one, however if I'm making a quick change that I know I want pushed, I will simply do a git commit -am "some comment" and then push. Still two steps, but less than 3.
* Git will show you what files you renamed, even in finder. I just ran a test where I renamed one file to another name. I added the file of the new name (build.xml -> ron.xml - added ron.xml after git told me it was deleted). Running git diff -M HEAD^ after commit shows me the following:
git diff -M HEAD^
diff --git a/build.xml b/ron.xml
similarity index 100%
rename from build.xml
rename to ron.xml
So it looks like git will tell you what was renamed, but there is still a manual add that is required and you have to tell it to tell you.
For #3, set up an account on Github and go to http://github.com/andymatuschak/Sparkle. You'll see a 'fork' button at the top of the screen. When you press it, you'll have your own sparkle repository. You can then clone it locally, work on branches there, and when you are done push it to your github repository. You can then send the author a 'pull request' to pull your changes from your public github repository.
Hope this helps.
|Colin Wheeler writes:|
you can do git commit -a -m 'commit msg' to both stage all the (tracked) files and then commit them. I usually just do 'git commit -a -m 'msg'' then 'git push origin master' periodically. You've got to remember that with git you have control over all the commits and can fix/ammend them before pushing to a public location (so it looks like you write perfect code all the time.)
the stage/commit/push is a workflow that's been accepted as common practice in DVCS's. In git's case that it there to give you full control over what gets comitted every time vs commiting all changes every time.
git will catch name changes since it's tracking content & not files. It'll auto discover that there is a new file with the content of part of it's last tree and if you commit from the terminal it'll just say 'renaming old.m => new.m'
on github you fork a project and can commit to it like crazy. Github has it's own mechanisms on the page where you can request that Andy (the sparkle owner) can pull your changes and merge it into his and then you can pull his changes. Github has some info on this ( http://help.github.com/forking/ .) They have the mechaisms on their page to easily do all of this without going to the command line.
if you need more help this is a great site for git rescources http://gitready.com/
|Patrick Stein writes:|
git remote add --help
I use different remotes for the different backup systems. I
for my default server. And
git push gitmasterbackup
to my backupserver for example.
About the two/three phase commit push. I do think it's worth it to first ensure locally that the commit went through ( maybe you have a git commit hook ) and then use the push and see what errors might happen there. Btw. on the commandline it's usually a four step move for me.
git add -A / git add -u / git add filename
git commit -m
I wonder if you can create a git commit hook to push it after commiting. Then a git commit -am would suffice.
Regards - Patrick Stein
author of: ScreenRecycler, JollysFastVNC, SmartSleep, SmartSokoban and more.
|Vincent Driessen writes:|
Hi there Uli,
If you are just starting with Git, you probably don't see the use of the staging area (the index), but as your usage of Git is getting more advanced, you will start appreciating it to tailor your commit together exactly as you want it before actually committing. Especially, the use of git add -p is a blessing.
If you are still looking for a Git workflow for bringing forth your software releases, you might find a write-up I did about a month ago on our successful branching strategy interesting. It's available at http://nvie.com/archives/323
|Rafael Bugajewski writes:|
After I switched from SVN to Git I also had this three steps procedure in mind and it bothered me. Why shall I do three steps when I can do everything with just one command? After a couple of weeks I realized that using a DVCS the SVN way isn’t the right thing to do. It is more a change of mind and how you think about your version control than a simple change of tools (like a CVS -> SVN switch). I realized that it is a lot easier and faster to do more commits with Git, so my workflow changed dramatically and I do a commit every time I do a minor, but complete, change in code. No matter if this is a small bug fix or a a new class with a couple of methods in it, I just do the commit of the current runnable version and continue to work. E.g. when I worked with SVN and wanted to introduce a new feature I did only one commit after I modified the NIBs, added the controllers and put in a lot of custom code. With Git I do a commit after I modify the NIBs, then I do a commit after I add the controllers, and so on… If you adopt this workflow then Git is really just a one-step show. You commit a lot during the day and then you push everything to a central server or to your co-worker or wherever you wish. This has also the benefit that you haven’t to remember or diff the changes everytime you do a bigger task. And if you/your company doesn’t like 20 commits for a small feature then you can just rewrite your history, merge your commit messages and treat it as one commit at the end of the day.
Like other already told you, Git should handle renames even if you do not rename via the Git command.
It is a little bit off-context, but I wanted add my 2 cents to our Twitter / Gitosis conversation. Gitosis is basically a very thin layer to manage your Git repositories. It works similar to the Git server you’ve set up (I mean what’s a “git server”? It’s just another repository located somewhere). It has the benefit that you can manage your repositories from within Git. You just modify a config file, push it to the server and everything is set up for you. This is very handy, because you’ll have a lot of Git repositories. You shouldn’t use Git like SVN where there is only one repository that holds everything, instead you should create a new repository for every significant module in your code (speaking of huge projects).