[TOC] Suggested Configuration -------------------------------------------------------------------------------- Configure with your work email, per [first-time Git setup](https://git-scm.com/book/en/v2/Getting-Started-First-Time-Git-Setup): > git config --global user.name "John Doe" > git config --global user.email johndoe@icl.utk.edu To prevent unintended merges, configuring "fast-forward only" globally is [highly recommended](https://blog.sffc.xyz/post/185195398930/why-you-should-use-git-pull-ff-only-git-is-a): > git config --global pull.ff only Having a graphical view of branches is indispensable. Ways to do this include: * Set up glog (for "graphical log") as an alias [[ref]](https://git-scm.com/docs/pretty-formats). See ~/.gitconfig below. * Use a GUI such as [Sourcetree](https://www.sourcetreeapp.com/), [Github Desktop](https://desktop.github.com/), [etc.](https://git-scm.com/downloads/guis) Using a graphical diff for resolving merges is also invaluable. There are several tools available: meld (recommended), opendiff (macOS only), tkdiff, etc. git diff # prints diff in shell git difftool # uses graphical diff git config --global diff.tool meld git config --global difftool.prompt false git mergetool # uses graphical 3-way merge git config --global merge.tool meld git config --global mergetool.prompt false Other useful git aliases and settings in ~/.gitconfig: [user] name = John Doe email = johndoe@icl.utk.edu [pull] ff = only [alias] id = rev-parse --short HEAD # hash of current commit idx = log --oneline -n 1 # log of current commit st = status ls = ls-files co = checkout branches = branch --all -v # Fast-forward a branch, master to origin/master after `git fetch origin` # Usage: git fwd master dev/master fwd = "!git fetch . \"$2:$1\"" mt = mergetool # for resolving merge conflicts meld = difftool -t meld -d # opendiff is graphical diff in macOS Xcode od = difftool -t opendiff -d # hash, author-date, author-name subject branches yyyy-mm-dd hh:mm glog = log --all --graph --decorate --format=format:'%C(yellow)%h%C(reset) %C(blue)%ad%C(reset) %C(red)%aN%C(reset) %s %C(bold)%d%C(reset)' --date=format:'%Y-%m-%d %H:%M' # https://stackoverflow.com/questions/34119866/setting-up-and-using-meld-as-your-git-difftool-and-mergetool [difftool "meld"] cmd = meld "$LOCAL" "$REMOTE" [mergetool "meld"] # Choose one of these 2 lines (not both!) explained below. # Each is 3 panes; the middle is either the merged or the original base. cmd = meld "$LOCAL" "$MERGED" "$REMOTE" --output "$MERGED" #cmd = meld "$LOCAL" "$BASE" "$REMOTE" --output "$MERGED" [mergetool] prompt = false [color] ui = true I tend to use ssh for accessing Bitbucket repos, as it eliminates repeatedly entering my password. [Online docs](https://support.atlassian.com/bitbucket-cloud/docs/set-up-an-ssh-key/) cover this topic in more detail. 1. Generate an ssh key on your computer using `ssh-keygen`. Copy the resulting public key from `~/.ssh/id_rsa.pub` to Bitbucket > Personal settings > SSH keys, as shown below. ![ssh keys](img/bitbucket-ssh-key.png) 2. [Optional] On ICL machines, you can add this public key to `~/.ssh/authorized_keys` to avoid the need to enter your password every time you `ssh saturn`. 3. Start ssh-agent, if needed. This is already done on macOS. Check `$SSH_AUTH_SOCK`. Otherwise, run (e.g., in ~/.profile or ~/.login): eval `ssh-agent -t 28800` # 8 hours = 28800 seconds 4. Use `ssh-add` to enter your password, adding that identity to the ssh-agent. Now you should not have to re-enter your password for every `git push` and `ssh saturn`. 5. Bitbucket's Clone button has a menu to choose between SSH and HTTPS URLs: * `git clone git@bitbucket.org:icl/slate.git # ssh` * `git clone https://mgates3@bitbucket.org/icl/slate.git # https` * `git clone https://bitbucket.org/icl/slate.git # https (anonymous)` ![clone](img/bitbucket-clone.png) Workflow -------------------------------------------------------------------------------- There is a [slate-dev](https://bitbucket.org/icl/slate-dev) fork for development. Do all development in a feature-specific branch, not in the master branch, to minimize conflicts. Alternatively, instead of using the slate-dev fork, create your own fork of the main slate repo. This incurs some extra overhead in setting up Jenkins for your fork. The purpose of having a fork instead of working in the main repo is to allow work to be rebased or abandoned. Once commits have been pushed into the main repo, they are public, **cannot** be rebased, and are not easily abandoned since other people may have cloned them. To demonstrate, I created a simple [workflow repo](https://bitbucket.org/mgates3/workflow) and a [dev fork](https://bitbucket.org/mgates3/workflow-dev) of it. Checkout the dev fork. I prefer renaming "origin" to something descriptive. > git clone --recursive git@bitbucket.org:mgates3/workflow-dev.git > cd workflow-dev > git remote rename origin dev > git remote -v dev git@bitbucket.org:mgates3/workflow-dev.git (fetch) dev git@bitbucket.org:mgates3/workflow-dev.git (push) Add link to main bitbucket repo, as desired. > git remote add bitbucket git@bitbucket.org:mgates3/workflow.git > git remote -v bitbucket git@bitbucket.org:mgates3/workflow (fetch) bitbucket git@bitbucket.org:mgates3/workflow (push) dev git@bitbucket.org:mgates3/workflow-dev.git (fetch) dev git@bitbucket.org:mgates3/workflow-dev.git (push) At this point, say these commits exist: > git glog * 6d9716f 2020-10-27 14:33 Mark Gates add b.txt (HEAD -> master, dev/master, dev/HEAD) * a507af6 2020-10-27 14:32 Mark Gates add a.txt Create a branch, to alleviate confusion about the local master branch diverging from the remote master branch: > git switch -c my-branch equivalent to: > git branch my-branch > git checkout my-branch Commit changes: > git add c.txt # add new file > git status -s # check what is being committed A c.txt > git commit -m 'add c.txt' c.txt > git add d.txt > git status -s A d.txt > git commit -m 'add d.txt' d.txt > git glog * 0389be6 2020-10-27 15:25 Mark Gates add d.txt (HEAD -> my-branch) * b6f68d4 2020-10-27 15:25 Mark Gates add c.txt * 6d9716f 2020-10-27 14:33 Mark Gates add b.txt (dev/master, dev/HEAD, master) * a507af6 2020-10-27 14:32 Mark Gates add a.txt See [docs for git status codes](https://git-scm.com/docs/git-status). You can confirm what was committed: > git show HEAD # diff current ("HEAD") commit > git show b6f68d4 # diff any commit by id If anything was ommitted, the last commit can be ammended, which replaces it with a new commit. (**Do not** amend commits that have been published in the main repo!) > git commit --amend d.txt (Advanced: Editing commits further back in history is possible with, e.g., `git rebase -i HEAD~5`, where 5 is the number of commits to go back. Again, **do not** edit commits that have been published.) Fetch updates from the main repo, with fast-forward only. The master pointer is moved ("fast-forward") to the latest commit. In this case, two commits (e and f) have been added since forking, causing my-branch and master to diverge: > git checkout master > git pull [--ff-only] bitbucket master > git glog * 0389be6 2020-10-27 15:25 Mark Gates add d.txt (my-branch) * b6f68d4 2020-10-27 15:25 Mark Gates add c.txt | * 0ee2c41 2020-10-27 14:36 Mark Gates add f.txt (HEAD -> master, bitbucket/master) | * f673e05 2020-10-27 14:36 Mark Gates add e.txt |/ * 6d9716f 2020-10-27 14:33 Mark Gates add b.txt (dev/master, dev/HEAD) * a507af6 2020-10-27 14:32 Mark Gates add a.txt This `git pull` is equivalent to: > git fetch bitbucket master # downloads commits > git merge [--ff-only] FETCH_HEAD # merges those with current branch ### Rebase my-branch can be rebased onto master (preferred) or merged with master. Either will require resolving conflicts. To rebase `my-branch` onto `master` branch: > git checkout my-branch > git rebase master my-branch > git glog * e6428a4 2020-10-27 15:25 Mark Gates add d.txt (HEAD -> my-branch) * e93b752 2020-10-27 15:25 Mark Gates add c.txt * 0ee2c41 2020-10-27 14:36 Mark Gates add f.txt (bitbucket/master, master) * f673e05 2020-10-27 14:36 Mark Gates add e.txt * 6d9716f 2020-10-27 14:33 Mark Gates add b.txt (dev/master, dev/HEAD) * a507af6 2020-10-27 14:32 Mark Gates add a.txt Note the commit ids for c and d changed. If there is a conflict, you can either manually edit the conflicting files (ugh!), or use a graphical 3-way merge tool (recommended). See configuration above to choose merge tool. > git rebase master CONFLICT (add/add): Merge conflict in e.txt ... > git status -s AA e.txt > git mergetool # runs graphical merge tool; edit & save changes > git status -s M e.txt > git rebase --continue # repeat `git mergetool` and `git rebase --continue` until all # commits have been resolved. ### Merge Alternatively, you can merge the master into my-branch, to get the latest updates. (Merging my-branch into master should happen via a pull request.) > git checkout my-branch > git merge master > git glog * 7ce3197 2020-10-27 15:28 Mark Gates Merge branch 'master' into my-branch (HEAD -> my-branch) |\ | * 0ee2c41 2020-10-27 14:36 Mark Gates add f.txt (bitbucket/master, master) | * f673e05 2020-10-27 14:36 Mark Gates add e.txt * | 0389be6 2020-10-27 15:25 Mark Gates add d.txt * | b6f68d4 2020-10-27 15:25 Mark Gates add c.txt |/ * 6d9716f 2020-10-27 14:33 Mark Gates add b.txt (dev/master, dev/HEAD) * a507af6 2020-10-27 14:32 Mark Gates add a.txt Note the commit ids for c and d remain the same, and a new merge commit is added. Again, if there is a conflict, either resolve it manually, or use a graphical 3-way merge tool, then commit changes. > git merge master CONFLICT (add/add): Merge conflict in e.txt Auto-merging e.txt Automatic merge failed; fix conflicts and then commit the result. > git status -s AA e.txt A f.txt > git mergetool # runs graphical merge tool; edit & save changes > git status -s M e.txt A f.txt > git commit -m 'merge master into my-branch' ### Undo rebase or merge Note you can undo a rebase or a merge. Just checkout the original commit hash and force switch the branch back. Thus it's helpful to do `git glog` before doing a rebase, and temporarily saving the output until you're satisfied the rebase was good. > git checkout -B my-branch 0389be6 Reset branch 'my-branch' > git glog * 0389be6 2020-10-27 15:25 Mark Gates add d.txt (HEAD -> my-branch) * b6f68d4 2020-10-27 15:25 Mark Gates add c.txt | * 0ee2c41 2020-10-27 14:36 Mark Gates add f.txt (bitbucket/master, master) | * f673e05 2020-10-27 14:36 Mark Gates add e.txt |/ * 6d9716f 2020-10-27 14:33 Mark Gates add b.txt (dev/master, dev/HEAD) * a507af6 2020-10-27 14:32 Mark Gates add a.txt ### Push commits Once satisfied, push changes to the dev fork: > git push dev my-branch and create a pull request (PR) on bitbucket. Changes can continue to be pushed to my-branch on the dev fork, which will update the PR. If commits have previously been pushed, but then you rebased those commits, you need to force push (**but see cautions below!**): > git push -f dev my-branch On Jenkins, check that the branch passes tests, and copy the Jenkins URL into the PR to show that it has passed. ### Caution! **Do not** rebase commits that other SLATE developers have started to use. **Do not** rebase commits that have already been published in the main repo. Since others may have downloaded those commits, they are final. **Do not** force push to the main repo. In fact, do not push to the main repo; use pull requests. Also, please do not use Bitbucket `Sync` feature on the fork, shown below. Doing so adds needless and confusing merge commits, rather than simply fast-forwarding the master branch to the latest version. Use the above procedure to fast-forward. ![not sync](img/bitbucket-sync-not.png) Moving branch pointers -------------------------------------------------------------------------------- I find that I have to move branch pointers around from time to time. I'm really not sure if this is usual, or because I fight with git, but it happens. Just use one of the force commands: To create and checkout a branch at the current commit: # capital -C forces creating my-branch here, if it already exists. > git switch -C my-branch equivalent to: # -f forces creating my-branch here, if it already exists. > git branch -f my-branch > git checkout my-branch To create and checkout a branch at, say, commit 90a83cd: # capital -B forces creating my-branch, if it already exists. > git checkout -B my-branch 90a83cd equivalent to: > git checkout 90a83cd > git branch -f my-branch > git checkout my-branch Pushing to other servers -------------------------------------------------------------------------------- With Mercurial, I found it very convenient to push commits from my laptop to other machines like saturn. Unfortunately, this is more fragile in Git, even discouraged. Some say push only to "bare" git repos, which are created with `git init --bare` and have no working files, just the contents of the normal `.git` folder. In that case, push local changes to bitbucket or some other bare repo, then on saturn, pull from that bitbucket or bare repo. However, by switching branches on the remote repo, you can push to a (working, non-bare) remote repo. On local system, one time setup: > git remote add saturn saturn.icl.utk.edu:repos/workflow-dev > git remote -v bitbucket git@bitbucket.org:mgates3/workflow.git (fetch) bitbucket git@bitbucket.org:mgates3/workflow.git (push) dev git@bitbucket.org:mgates3/workflow-dev.git (fetch) dev git@bitbucket.org:mgates3/workflow-dev.git (push) saturn saturn.icl.utk.edu:repos/workflow-dev (fetch) saturn saturn.icl.utk.edu:repos/workflow-dev (push) On remote system (e.g., saturn), switch branches: saturn> git checkout master # some branch other than my-branch or saturn> git switch -c tmp # or create and checkout tmp branch On local system, push changes: > git checkout my-branch > git push saturn my-branch Back on saturn, checkout changes: saturn> git checkout my-branch If you try to push my-branch when saturn has my-branch checked out, git will refuse (by default).