Time to study: ~90 min
You will learn: what a remote really is, the SSH key, push pull fetch clone, why a push gets rejected, .gitignore, and the gh command line tool.
← Chapter 3 | back to Git 101 | next: Chapter 5 →
1. what is github
github was created in
2008by Tom Preston-Werner, Chris Wanstrath, PJ Hyett and Scott Chacon, and went live on 10 April 2008.
it is a code hosting platform for version control and collaboration. think of it as instagram but for code: u post ur projects, u share them with people, and if they like what u did they give ur repo a star.
- and no, github is not the only one. there is
GitLab,Bitbucket,SourceForge, self hosted ones likeGitea, and even AWS and Google have their own. github is just the most popular by a lot. - gitlab is the other big one, especially inside companies, because u can self host it for free and it is fully open source. it started in 2011 as an open source alternative to github, and it packs everything in one box (CI/CD, security, issue tracking), where github leans more on third party integrations.
2. a remote is not github
before the sign up steps, get this straight, because it is the thing that makes the whole chapter make sense:
a remote is just another copy of ur repository that ur repo knows the address of. that is the entire definition. it is usually on github, but nothing says it has to be.
example: i have a
~/Attia/work/directory with a git repo in it. i want a second copy in~/Attia/work_cloneto experiment in without risking the real one:
cd ~/Attia/work_clone
git init
git remote add origin ~/Attia/workthat is a real remote. it is a folder on my own laptop, no internet involved, and
pushandpullwork with it exactly the way they work with github. github is popular, not special.
originis just a name for that address. it is the default name everyone uses for “the main remote”, it is not a keyword, and u can call it whatever u want.
3. connecting ur machine to github
- go to github.com and create an account with ur email, username and password.
- verify ur email, and u are in.
- make sure git is installed (
git --version). if u did Chapter 1 it already is. - set ur identity, which we also already did in Chapter 1 and Chapter 2:
git config --global user.name "ur name"
git config --global user.email "ur_email@example.com"- create an
SSHkey. this is the important step:
ssh-keygen -t ed25519 -C "ur_email@example.com"Generating public/private ed25519 key pair.
Enter file in which to save the key (/home/ati/.ssh/id_ed25519):
Enter passphrase (empty for no passphrase):
Enter same passphrase again:
Your identification has been saved in /home/ati/.ssh/id_ed25519
Your public key has been saved in /home/ati/.ssh/id_ed25519.pub
press Enter 3 times to accept the defaults. what is an SSH key? it is how github knows this laptop is yours. think of it like this: ur account is a house, ur laptop wants to get in, and the SSH key is the key that proves it is allowed. u keep the private half (
id_ed25519) and u never show it to anyone. u give github the public half (id_ed25519.pub).
- copy the public key:
cat ~/.ssh/id_ed25519.pubssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx ur_email@example.com
-
go to
GitHub → Settings → SSH and GPG keys → New SSH key, paste it, save. -
check that it worked:
ssh -T git@github.comHi Mohamedattiadev! You've successfully authenticated, but GitHub does not provide shell access.
that sentence looks like an error and it is not. “does not provide shell access” is normal, u are not supposed to get a terminal on github’s servers. the part that matters is Hi <ur name>.
4. pushing ur project up
from Chapter 1 we have: untracked →
add→ staged →commit→ local repo.pushis the step after that:
Local Repo Remote Repo
┌──────────────┐ ┌──────────────┐
│ Readme.md │───push──────►│ Readme.md │
│ (committed) │ │ (remote) │
└──────────────┘ │ GITHUB │
└──────────────┘
to push, u first need a remote to push to. so:
- on github, click New repository, give it a name.
- choose
public(anyone can see it) orprivate(only u and who u invite). - do not tick “add a README” for now. an empty repo is easier for ur first push.
- github shows u 2 addresses:
https://github.com/username/repo-name.git <- the https one
git@github.com:username/repo-name.git <- the ssh oneuse the ssh one, that is what the key we just made is for.
- back in ur terminal, inside ur project:
git remote add origin git@github.com:username/repo-name.git
git remote -vorigin git@github.com:username/repo-name.git (fetch)
origin git@github.com:username/repo-name.git (push)
- and push:
git push -u origin mainTo git@github.com:username/repo-name.git
* [new branch] main -> main
branch 'main' set up to track 'origin/main'.
refresh the github page and ur files are there.
- what is the
-u? it links ur localmaintoorigin/mainonce, so that from then on plaingit pushand plaingit pullknow where to go. u only need it the first time.
git push # from now on, this is enough- if u forget the remote entirely, git tells u exactly this:
git pushfatal: No configured push destination.
Either specify the URL from the command-line or configure a remote repository using
git remote add <name> <url>
- and if u typed the url wrong, u do not start over, u fix it:
git remote set-url origin git@github.com:username/the-right-name.git
git remote -v # check it- to see the remote branch in ur status,
git statusnow says something new:
On branch main
Your branch is up to date with 'origin/main'.
nothing to commit, working tree clean
5. clone, fetch and pull
clone takes a repo that exists somewhere and makes a full copy on ur machine, history and all:
git clone git@github.com:username/repo-name.gitCloning into 'repo-name'...
done.
# or put it where u want, with the name u want
git clone git@github.com:username/repo-name.git ~/Desktop/my-copyu do not run
git initafter a clone. the clone is the repo, andoriginis already set for u:
cd repo-name
git remote -vorigin git@github.com:username/repo-name.git (fetch)
origin git@github.com:username/repo-name.git (push)
fetch downloads what is new on the remote but does not touch ur files:
git fetchFrom github.com:username/repo-name
47200d3..1893de3 main -> origin/main
git statusOn branch main
Your branch is behind 'origin/main' by 1 commit, and can be fast-forwarded.
(use "git pull" to update your local branch)
pull is fetch plus merging it into ur branch. it is the one u will use:
git pullUpdating 1893de3..cdfc65d
Fast-forward
Readme.md | 1 +
1 file changed, 1 insertion(+)
- so the rule:
fetch= “show me what happened, do not touch my work”.pull= “and put it into my branch now”.
6. the rejected push, and why it happens
this is the first github error that will scare u, so let’s cause it on purpose. someone else pushed (or u pushed from another laptop), and then u push:
git pushTo github.com:username/repo-name.git
! [rejected] main -> main (fetch first)
error: failed to push some refs to 'github.com:username/repo-name.git'
hint: Updates were rejected because the remote contains work that you do not
hint: have locally. This is usually caused by another repository pushing to
hint: the same ref. If you want to integrate the remote changes, use
hint: 'git pull' before pushing again.
git is protecting u. the remote has a commit u have never seen, and pushing would throw it away. so:
git pull # bring their work into yours
git push # now u are on top of it, and it worksif u both edited the same lines, the
pullgives u a conflict. that is not a disaster and it is the whole of Chapter 5, section 6.
- never fix a rejected push with
git push --force. force means “delete whatever is on the remote and put mine there instead”, and what u delete is someone else’s work. there is a safer version,--force-with-lease, and the one time u will legitimately need it is after rewriting ur own history in Chapter 6.
7. .gitignore, the files u keep out
u will notice that when u run a real project u get a
node_modulesfolder with thousands of files in it, or avenv, or abuildfolder. that stuff does not belong in git: it is huge, it is generated from ur code anyway, and everyone who clones ur repo regenerates it with one command.
and worse than huge, some files are secret. a
.envwith ur database password does not belong on github, ever.
so u make a file called
.gitignoreand list what git should pretend it cannot see:
git status -s?? .env
?? debug.log
?? node_modules/
?? src/
cat > .gitignore <<'END'
node_modules/
*.log
.env
END
git status -s?? .gitignore
?? src/
the noise is gone.
node_modules/,debug.logand.envare still on disk, git just stopped offering them to u.
the patterns u need:
node_modules/ a folder and everything in it (the trailing / means folder)
*.log ANY file ending in .log, anywhere
.env this exact name
build/ the build output
*.zip every zip
!important.log an EXCEPTION: ignore all .log, but keep this one
*.thingis the one u will use most: the*means “anything”, so*.logcatchesdebug.log,error.logandwhatever.login one line.- when u cannot work out why a file is being ignored, ask git, it tells u the exact file and line number of the rule:
git check-ignore -v node_modules/a.js debug.log .env.gitignore:1:node_modules/ node_modules/a.js
.gitignore:2:*.log debug.log
.gitignore:3:.env .env
and u commit the
.gitignoreitself, so everyone who clones the project ignores the same things.
the trap: ignoring does not untrack
.gitignoreonly works on files git is not already tracking. if u committed.envlast week and add it to.gitignoretoday, git keeps tracking it and keeps showing ur password to everyone:
git status -s M .env <- still tracked, .gitignore did nothing
u have to tell git to stop tracking it, without deleting ur file:
git rm --cached .envrm '.env'
git status -sD .env
read that
D. it is a staged deletion: u told git to stop tracking the file, and that removal is now waiting to be committed like any other change. so commit it:
git commit -m "stop tracking .env"
git status -s(nothing)
and nothing is exactly right. the file is still sitting on ur disk, git is no longer tracking it, and
.gitignoreis now free to hide it:
ls -a. .. .env .git .gitignore node_modules src
--cachedis the whole trick: “remove it from git, leave it on my disk”. without that flag,git rmdeletes the actual file.
- and a warning that matters: this stops it from now on, it does not remove it from the commits u already pushed. if a real password was in there, change the password. that is the only real fix.
a .gitignore per folder
u can put a
.gitignorein any folder, not only the root:
my-project/
├── .gitignore <- rules for the whole repo
└── src/
└── .gitignore <- extra rules, only for src/ and under it
the root one applies everywhere. the one inside
src/adds rules that only exist insidesrc/. git checks both:
git check-ignore -v src/app.jssrc/.gitignore:1:*.js src/app.js
.git/info/exclude, the one that is only urs
this one i really like.
.gitignoreis committed, so its rules apply to everyone. sometimes u want to ignore something that is only ur business: ur own scratch file, ur editor’s junk, a personaltodo.md.
putting that in
.gitignoremeans pushing ur personal habits into everyone else’s repo. so instead, u write it in a file that is inside.gitand therefore never leaves ur machine:
echo "my-scratch.md" >> .git/info/excludegit check-ignore -v my-scratch.md.git/info/exclude:7:my-scratch.md my-scratch.md
same behaviour, invisible to everyone else. u never have to explain to ur team why
ati-notes.mdis in their ignore file.
.gitignore -> committed, applies to everyone who clones
.git/info/exclude -> local, only u, never pushed
8. pull requests, the short version
u pushed to ur own repo, so u pushed straight to
main. on any project with more than one person, u do not do that. u push a branch, and then u open a pull request: “here is my work, please look at it and merge it”.
u make a branch -> push the branch -> open a PR -> someone reviews it
↓
they merge it into main
the shape of it, with the branch commands from Chapter 5:
git switch -c feat/dark-theme # a branch for this one job
# ... work, add, commit ...
git push -u origin feat/dark-themeremote:
remote: Create a pull request for 'feat/dark-theme' on GitHub by visiting:
remote: https://github.com/username/repo-name/pull/new/feat/dark-theme
remote:
To github.com:username/repo-name.git
* [new branch] feat/dark-theme -> feat/dark-theme
github prints the link in ur terminal. open it, write what u changed and why, and create the PR.
a PR is 3 things at once: a diff people can read, a place to discuss it line by line, and the button that merges it. that is why teams work this way instead of everyone pushing to
mainat the same time.
- doing this on someone else’s project needs one extra step first, called a fork, and that is the whole of Chapter 9, together with how to write a PR that actually gets accepted.
9. gh, github from ur terminal
ghis github’s own command line tool, and it is genuinely easier than the website for the things u do 20 times a day. install it (sudo pacman -S github-cli,sudo apt install gh,brew install gh), then log in once:
gh auth loginit asks u 4 questions (github.com, SSH, use ur key, log in with a browser) and it opens ur browser with a code. after that:
gh auth statusgithub.com
✓ Logged in to github.com account Mohamedattiadev
- Active account: true
- Git operations protocol: https
- Token scopes: 'gist', 'read:org', 'repo', 'workflow'
now the things u actually want:
# create a repo FROM the folder u are standing in, and push it, in one command
gh repo create my-project --public --source=. --push
# clone without typing the full url
gh repo clone Mohamedattiadev/dev-101
# open the current repo's page in ur browser
gh repo view --web
# open a pull request from the branch u are on, and fill it in interactively
gh pr create
# see the PRs waiting, and check one out locally to test it
gh pr list
gh pr checkout 42
# the issues
gh issue list
gh issue creategh repo create --source=. --pushreplaces the whole of section 4: no clicking on the website, no copying the url, nogit remote add. it makes the repo, adds the remote and pushes.ghis a convenience on top of git, not a replacement. every one of those commands is something u can do without it, and u should know how, which is why it comes at the end of the chapter and not the start.
NOTES
-
https vs ssh. the https url asks for a token every time (github stopped accepting passwords in 2021). the ssh one uses the key u made and never asks. use ssh.
-
originis a name u chose. u can have more than one remote, and u will in Chapter 9, whereoriginis ur fork andupstreamis the original project:
git remote add upstream git@github.com:someone-else/the-project.git
git remote -v-
git remote remove origindeletes the link. it does not touch ur commits or the remote repo, it only forgets the address. -
a repo with no commits cannot be pushed.
git pushon an empty repo pushes nothing and looks broken. commit first. -
README.md is not a git feature. github just renders the file called
README.mdon the repo’s front page. that is the whole trick, and it is why every project has one.
Assignment
- Make an SSH key if u do not have one, and prove github recognises u with it.
- Create a repo on github, empty, and connect ur
practice-gitfolder from Chapter 1 to it. - Show the remote address ur repo is now pointing at.
- Push ur
mainbranch, and set it up so that plaingit pushworks from then on. - Change the remote url to something wrong, check it, then set it back.
- Clone ur own repo into a different folder under a different name.
- In the clone, make a commit and push it. Then, in the original folder, run
git fetchand read whatgit statussays. - Pull it in properly.
- Create a
.envfile with a fake password in it, and anode_modulesfolder with a junk file. Make git ignore both of them. - Prove which line of which file is doing the ignoring.
- Commit
.envby accident (git add -f .env), then stop tracking it without deleting the file. - Ignore a personal scratch file in a way that is not visible to anyone who clones ur repo.
stuck, or done and want to check? the solutions are here