How do you create a new project/repository?
A git repository is simply a directory containing a special .git
directory.
This is different from "centralised" version-control systems (like subversion), where a "repository" is hosted on a remote server, which you checkout
into a "working copy" directory. With git, your working copy is the repository.
Simply run git init
in the directory which contains the files you wish to track.
For example,
cd ~/code/project001/
git init
This creates a .git
(hidden) folder in the current directory.
To make a new project, run git init
with an additional argument (the name of the directory to be created):
git init project002
(This is equivalent to: mkdir project002 && cd project002 && git init)
To check if the current current path is within a git repository, simply run git status
- if it's not a repository, it will report "fatal: Not a git repository"
You could also list the .git
directory, and check it contains files/directories similar to the following:
$ ls .git
HEAD config hooks/ objects/
branches/ description info/ refs/
If for whatever reason you wish to "de-git" a repository (you wish to stop using git to track that project). Simply remove the .git
directory at the base level of the repository.
cd ~/code/project001/
rm -rf .git/
Caution: This will destroy all revision history, all your tags, everything git has done. It will not touch the "current" files (the files you can currently see), but previous changes, deleted files and so on will be unrecoverable!
The Easy Way™
It turns out that this is such a common and useful practice that the overlords of Git made it really easy, but you have to have a newer version of Git (>= 1.7.11 May 2012). See the appendix for how to install the latest Git. Also, there's a real-world example in the walkthrough below.
Prepare the old repo
cd <big-repo>
git subtree split -P <name-of-folder> -b <name-of-new-branch>
Note: <name-of-folder>
must NOT contain leading or trailing characters. For instance, the folder named subproject
MUST be passed as subproject
, NOT ./subproject/
Note for Windows users: When your folder depth is > 1, <name-of-folder>
must have *nix style folder separator (/). For instance, the folder named path1\path2\subproject
MUST be passed as path1/path2/subproject
Create the new repo
mkdir ~/<new-repo> && cd ~/<new-repo>
git init
git pull </path/to/big-repo> <name-of-new-branch>
Link the new repo to GitHub or wherever
git remote add origin <git@github.com:user/new-repo.git>
git push -u origin master
Cleanup inside <big-repo>
, if desired
git rm -rf <name-of-folder>
Note: This leaves all the historical references in the repository. See the Appendix below if you're actually concerned about having committed a password or you need to decreasing the file size of your .git
folder.
Walkthrough
These are the same steps as above, but following my exact steps for my repository instead of using <meta-named-things>
.
Here's a project I have for implementing JavaScript browser modules in node:
tree ~/node-browser-compat
node-browser-compat
├── ArrayBuffer
├── Audio
├── Blob
├── FormData
├── atob
├── btoa
├── location
└── navigator
I want to split out a single folder, btoa
, into a separate Git repository
cd ~/node-browser-compat/
git subtree split -P btoa -b btoa-only
I now have a new branch, btoa-only
, that only has commits for btoa
and I want to create a new repository.
mkdir ~/btoa/ && cd ~/btoa/
git init
git pull ~/node-browser-compat btoa-only
Next, I create a new repo on GitHub or Bitbucket, or whatever and add it as the origin
git remote add origin git@github.com:node-browser-compat/btoa.git
git push -u origin master
Happy day!
Note: If you created a repo with a README.md
, .gitignore
and LICENSE
, you will need to pull first:
git pull origin master
git push origin master
Lastly, I'll want to remove the folder from the bigger repo
git rm -rf btoa
Appendix
Latest Git on macOS
To get the latest version of Git using Homebrew:
brew install git
Latest Git on Ubuntu
sudo apt-get update
sudo apt-get install git
git --version
If that doesn't work (you have a very old version of Ubuntu), try
sudo add-apt-repository ppa:git-core/ppa
sudo apt-get update
sudo apt-get install git
If that still doesn't work, try
sudo chmod +x /usr/share/doc/git/contrib/subtree/git-subtree.sh
sudo ln -s \
/usr/share/doc/git/contrib/subtree/git-subtree.sh \
/usr/lib/git-core/git-subtree
Thanks to rui.araujo from the comments.
Clearing your history
By default removing files from Git doesn't actually remove them, it just commits that they aren't there anymore. If you want to actually remove the historical references (i.e. you committed a password), you need to do this:
git filter-branch --prune-empty --tree-filter 'rm -rf <name-of-folder>' HEAD
After that, you can check that your file or folder no longer shows up in the Git history at all
git log -- <name-of-folder> # should show nothing
However, you can't "push" deletes to GitHub and the like. If you try, you'll get an error and you'll have to git pull
before you can git push
- and then you're back to having everything in your history.
So if you want to delete history from the "origin" - meaning to delete it from GitHub, Bitbucket, etc - you'll need to delete the repo and re-push a pruned copy of the repo. But wait - there's more! - if you're really concerned about getting rid of a password or something like that you'll need to prune the backup (see below).
Making .git
smaller
The aforementioned delete history command still leaves behind a bunch of backup files - because Git is all too kind in helping you to not ruin your repo by accident. It will eventually delete orphaned files over the days and months, but it leaves them there for a while in case you realize that you accidentally deleted something you didn't want to.
So if you really want to empty the trash to reduce the clone size of a repo immediately you have to do all of this really weird stuff:
rm -rf .git/refs/original/ && \
git reflog expire --all && \
git gc --aggressive --prune=now
git reflog expire --all --expire-unreachable=0
git repack -A -d
git prune
That said, I'd recommend not performing these steps unless you know that you need to - just in case you did prune the wrong subdirectory, y'know? The backup files shouldn't get cloned when you push the repo, they'll just be in your local copy.
Credit
Best Answer
The remote branch from git-svn is pretty much the same as a regular Git remote. So in your local repository you can have your git-svn clone and push changes out to GitHub. Git doesn't care. If you create your git-svn clone and push the exact same changes out to GitHub, you'll have an unofficial mirror of the Google Code repository. The rest is vanilla Git.
Now that you have this, occasionally you will have to synchronise the Subversion repository with Git. It'll look something like:
In gitk or whatever, this would look something like this:
And when you run
git svn rebase
, you would have this:So now running
git push
would push those commits out to GitHub, the [remotes/origin/master] branch there. And you'd be back to the scenario in the first ASCII art diagram.The problem now is, how do you work your changes into the mix? The idea is, you don't ever commit onto the same branch that you are git-svn-rebase-ing and git-pushing. You need a separate branch for your changes. Otherwise, you would end up rebasing your changes on top of the Subversion ones, which could upset anyone who clones your Git repository. Follow me? OK, so you create a branch, let's call it "features". And you make a commit and push it out to GitHub to the features branch. Your gitk would look something like this:
Here you've got your features branch a couple of commits ahead of the Google Code branch, right? So what happens when you want to incorporate new stuff from Google Code? You'd run
git svn rebase
first and get this:If you
git push
master out, you can imagine the [remotes/origin/master] being at the same point as master. But your feature branch doesn't have the changes. Your choices now are to merge master into features, or rebase features. A merge would look like thisThen you push features out to GitHub. I've left off the remotes for master to save space, they'd be at the same point as [master].
The rebase approach is slightly more evil - you'd have to push with --force as your push would not be a fast-forward merge (you'd pull the features branch from under someone who had cloned it). It's not really considered OK to do this, but nobody can stop you if you are determined. It does make some things easier too, such as when patches get accepted upstream in slightly reworked form. It'd save having to mess about with conflicts, you can just rebase --skip the upstreamed patches. Anyway, a rebase would be like this:
And then you would have to
git push --force
that. You can see why you need to force it, the history has a big old schism from the [remotes/origin/features] to the new current post-rebase [features].This all works, but it is a lot of effort. If you are going to be a regular contributor, the best bet would be to work like this for a while, send some patches upstream and see if you can get commit access to Subversion. Failing that, perhaps don't push your changes out to GitHub. Keep them local and try and get them accepted upstream anyway.