0

I've been using TortoiseHg/Mercurial for many years and am now using Git for first time with SourceTree and TortoiseGit GUI. From this link I understand that PUSH is different on Git from Mercurial. Mercurial PUSH only affects the remote repository but doesn't touch the workspace. PUSH with Git on the other hand not only updates the repository but also the workspace (and index which Mercurial doesn't have).

My question is does Git have the equivalent of Mercurial PUSH? Can I PUSH to a remote repository and leave the workspace and index as it is? I know Git FETCH is the equivalent of Mercurial PULL in that neither of them modify the local workspace. Effectively I'm looking for a reverse FETCH for Git. Is that possible or must I always open the remote repository and do a FETCH instead?

JonN
  • 2,498
  • 5
  • 33
  • 49

3 Answers3

5

Edit: TL;DR summary:

  • When using Git, you can push to a repository humans use, that has a work-tree. You just should not push to the current branch within that repository, at least, not without special considerations. So Git blocks this by default.

  • Mercurial allows you to push to such repositories, including to the "current branch"—this phrase means something different in Mercurial—because in Mercurial, what Git would call a "detached HEAD" is a normal and usable state. This can be done in Git, it's just not very practical.

  • Therefore, Git offers, and you should generally use, bare repositories as push targets. These have no work-tree and therefore Git allows pushes to them, even to the "current branch" (Git's notion, rather than Mercurial's).

Original complete reply below.


As MrTux said, this claim is wrong:

PUSH with Git on the other hand not only updates the repository but also the workspace (and index which Mercurial doesn't have).

While pushing updates the repository (adding new commits as usual), it does not—at least, not by default—update the index, nor the work-tree. (It can be configured to do so in versions of Git starting from Git 2.4.0.)

The problem here is a mismatch between some fairly deep philosophical notions. The answer to this:

My question is does Git have the equivalent of Mercurial PUSH? Can I PUSH to a remote repository and leave the workspace and index as it is?

is "yes, using detached HEAD", but that answer is not as useful as you might think.

In Mercurial, the current revision (known as ., vs the name HEAD in Git) is checked-out, and possibly modified, in much the same way as it would be in Git if you were using a Git "detached HEAD" (though with some important differences). The index itself—the thing that exists and is exposed in Git, but does not exist in that form and is well-hidden in Mercurial—is not directly relevant.

The problem is that in Mercurial, the current branch is well-separated from the current revision. In Git, the two are deeply intertwingled. Clearly the current revision is on some branch: all revisions are on some branch, in Mercurial. But in Mercurial, all revisions are on exactly one branch. In Git, a revision—a commit—may be on many branches simultaneously: none, one, two, ten, or thousands. While the commit is unchanging and permanent (in both Git and Mercurial), the set of branches that contain the commit is fixed and unchanging in Mercurial, but totally fluid in Git.

Git (deliberately1) discards the idea that a branch name is anything more than a temporary, ephemeral label. The use of a branch name is just to identify one specific commit, for right now, mostly for humans to use, but also for use during git push and git fetch.

Therefore, since HEAD in Git contains the branch name—let's call this cb for Current Branch—and the branch name contains the commit ID, we will get in terrible trouble if we change the name-to-ID mapping in an unexpected manner. If receiving a push updates cb, while we are (through HEAD) using it as our current revision, we will get confused: we will believe that our index and work-tree apply to the commit to which cb now points, instead of the commit to which cb used to point before the update.

Again, this is not a problem in Mercurial, because in Mercurial, the system knows which is the current revision. Pushing new revisions (commits) into the branch does not affect our current revision, and in fact, if we make a new commit without updating, we just get a new commit that is a new head (lowercase, not Git's all-caps HEAD thing). This new head will have no name, unless we're using bookmarks, but having heads with no name is an ordinary state in Mercurial. We merely run hg heads to find these unnamed heads, and hg update -r to those revisions to work on them (such as to merge them, usually with other heads).

In Git, you can get precisely this state: it's that "detached HEAD". A detached HEAD contains a commit ID directly, rather than a branch name. Now that Git won't lose the current revision (or rather, commit hash ID), it's safe to push to this repository.

The problem is that when you have a detached HEAD, while you can make new commits, they're being remembered only by the name HEAD. They are on no branches. (If you prefer, you can call HEAD an "anonymous branch". It has the name HEAD but HEAD is not actually a branch name, as all branch names semi-secretly start with refs/heads/, and HEAD does not.) To make Git remember it more permanently, you must give this thing a branch name:

git checkout -b newbranch

which changes HEAD from "detached" to "attached", to the new branch newbranch. The branch name newbranch now stores the commit hash ID and the name HEAD now stores the branch-name newbranch.

But now you can't push to newbranch!

So as long as HEAD is not "detached", there is some branch you cannot, or at least should not, push to ... and for HEAD to work well, it must eventually be re-attached to a branch, maybe a new branch.

Note that you can push to any other branch. All the other branches are not in the index and not in the work-tree.

The new capability in Git 2.4.0 and later is that you can set receive.denyCurrentBranch to updateInstead. When it is set this way, a Git on the receiving end of git push checks:

  • Is the push going to the branch that is the current branch? That is, does HEAD have the name of a branch in it, and if so, is the push going to update this branch name?
  • If not (and all other tests pass), allow the update.
  • If so, check receive.denyCurrentBranch:
    • true or refuse: reject the push
    • false or ignore: accept the push, leaving the index and work-tree alone, relying on whoever is using this repository to know what to do
    • warn: accept the push, printing a warning to whoever is doing the push (ideally this is the same person who will know what to do, otherwise the warning is useless).
    • updateInstead: check one more thing (see below).

In the updateInstead mode, things get complicated. See the description in the git config documentation and the description of the push-to-checkout hook in the githooks documentation. The short version is that if the index and work-tree are clean, the push will generally be allowed and the receiving repository will be updated to the new branch-tip.

In general, though, your best bet is the much simpler rule: just don't do it.2 Don't push to a repository that has humans working in it. It's a bad idea. Push only to repositories that never have humans working in them. Configuring a Git repository as "bare" guarantees that no human can work in it, as it has no work-tree.


1This is either a wonderful idea, or a terrible idea, or perhaps both at the same time. Either way, it's radically different.

2This might be called the anti-Nike rule. :-)

Community
  • 1
  • 1
torek
  • 448,244
  • 59
  • 642
  • 775
  • I've now read this what definitely appears to be a detailed and accurate explanation through several times and I am realizing that my history with TortoiseHg/Mercurial is distorting my understanding. It feels like trying to drive a manual shift car after only driving an automatic. I find reading this answer I have many more questions. In some cases I'm not sure I even understand enough (yet) to ask an intelligent question. – JonN Dec 13 '16 at 02:06
  • 1. When you say. "In Git, a revision—a commit—may be on many branches simultaneously: none, one, two, ten, or thousands. While the commit is unchanging and permanent (in both Git and Mercurial), the set of branches that contain the commit is fixed and unchanging in Mercurial, but totally fluid in Git" Does this mean that the "checkout" for a workspace for Git is not tied to a specific commit ID but somehow indirectly tied to it by a branch name? So that is why if i push something to a remote branch it gets confused as to what commit was checked out? – JonN Dec 13 '16 at 02:12
  • 2. What exactly is the "index"? What does it hold. It seems nebulous and due to my Mercurial experience it seems unnecessarily complex and superfluous. My conjecture is it somehow holds the info related to the staged files. – JonN Dec 13 '16 at 02:16
  • @JonN: 1: sort of, yes. The ID of the current Git commit is recorded in a two-step fashion: `HEAD` says "branch `B`" and then branch `B` says `1234567...`. Git does this *so that* when you make a *new* commit—let's say the new one happens to be `fa1afe1`—Git can just write `fa1afe1` into `B` and now branch `B` points to the new commit and your current commit is the new commit. – torek Dec 13 '16 at 02:17
  • 2: "the index" *is* rather nebulous and poorly defined. It serves multiple roles in Git, most of which are sufficiently well hidden that you can ignore them, leaving only one that you must face: "the index" is where you build the *next* commit. `git commit` simply turns the index into a commit, while `git add` replaces the file-version in the index with the updated one from the work-tree. This is how you can `git add -p` *part* of a file: instead of adding the entire new file, Git adds just the selected parts (keeping the rest of the file the same). – torek Dec 13 '16 at 02:20
  • I guess "sort of" is too weak: from the right point of view, your point 1 is exactly right. To expand a bit on point 2, to see what's really in the index, you can run `git ls-files --stage --debug`: this shows you everything (in a format mostly comprehensible only to people trying to debug Git). The `--stage` part is the other place users will interact with the index, but this is used only during conflicted merges. If you don't merge, or don't hit conflicts, you'll never see non-zero stage numbers. – torek Dec 13 '16 at 02:27
  • 3. A practice I've had with TortoiseHg is when bug tracking to load my workspace with different historical commits. To do this I make sure there are no outstanding changes in my, workspace was "clean". Then do an Update to a previous changeset. Nothing in the repository changes but the workspace now points to an older commit. I could easily switch between any commit in the entire repository tree by doing another Update. I've been trying to do this with Git/SourceTree/TortoiseGit with limited success. 1st tried Reset to Commit but that deleted commits. Checkout doesn't seem to be it eithe – JonN Dec 13 '16 at 02:27
  • Discussion seems to have gone dead, but to answer #3: `git checkout` *is* the way to do it. You get a "detached HEAD" on the older, checked-out commit. For bug hunting, you generally want `git bisect`, which automates the process of finding the bad commit, to whatever extent possible. If you can write a test script, this is "to 100%" in most cases! Note that once HEAD is "detached", `git log` shows only from that point backwards: you must `git log ` to view, or `git checkout ` to reattach HEAD. – torek Dec 13 '16 at 02:58
  • 1b. “... the set of branches that contain the commit is fixed and unchanging in Mercurial, but totally fluid in Git.” I’m still pondering the full meaning of this. Does this for example mean I could take a development branch in Git and effectively rename it so that it becomes part of the master branch and have the development branch go away without actually deleting any of the commits? What commands would I use to do this? It seems different than a merge which still keeps the development commits as belonging to the development branch. – JonN Dec 13 '16 at 18:28
  • 3. When I do checkout I get a dialog in SourceTree that I find confusing. "Checkout Existing" or "CheckoutNewBranch". I just want it to load my workspace with the commit I pointed to. I don't want a new branch so I select "Checkout Existing". Then it brings up another dialog about multiple branches pointing at the commit selected. The options are Development branch or origin/Development branch. I selected Development. It completes but looking at the graph log it still shows the latest commit with bolded description and the workspace still contains the most recent revision. – JonN Dec 13 '16 at 18:41
  • 3. It sounds like **bisect** command requires an automated script to build and test which is not viable in my development system. The part about needing to create detached HEAD and the log not showing downstream commits at that point makes it sound like the work-flow I've used in TortoiseHg/Mercurial is not viable with Git. In TortoiseHg loading my workspace with an older commit shows the same graph but with an older commit with bolded description indicating where workspace was loaded from. I don't need a new HEAD as I'm just running tests for the bug not committing changes – JonN Dec 13 '16 at 19:02
  • Re 1b: think of the names as someone hinting "you should look at commit #1234567". Today he says "1234567" when you ask about master, tomorrow he says "9876543". If you remember the numbers yourself, you can track real relationships. If you just use his hints, you're depending on *him* to remember the numbers accurately. Which is fine, except he's taking orders from Joe over in Department Q, and Joe may arbitrarily wipe his memory with a force-push. So you're also trusting Joe! Re 3 and SourceTree: I don't use SourceTree and don't know what they overlay atop what Git actually provides. – torek Dec 13 '16 at 19:13
  • Re 3: Bisect *does not* **require** a script. A script is just a *good idea*. You can manually check each revision and say `good` or `bad`. Note that instead of `git log` (which means `git log HEAD`), you can just run `git log master` to see everything reachable from the name `master`. (And remember, your branch names are *your* memory for commit #s, it's when you trust `origin/master` that you're getting someone else's memory.) – torek Dec 13 '16 at 19:16
  • My confusion is from a misconception of the definition of a Git "branch". Exactly what info is stored in a branch? My flawed conception was a branch was a set of connected commits with a branch name and that branches were global and workspace pointed to a specific commit within a branch. What I'm hearing now is that they are local (even though they are seen from other repos). I'm still confused as to how workspace points to a commit via branch because I don't have branch def. Must WS be at head of the current branch or use detached HEAD? Is workspace's head pointer part of branch info? – JonN Dec 13 '16 at 19:49
  • You may want to review http://stackoverflow.com/questions/25068543/what-exactly-do-we-mean-by-branch next... – torek Dec 13 '16 at 19:56
  • yes that other question was most helpful as was this link http://think-like-a-git.net/sections/rebase-from-the-ground-up/cherry-picking-explained.html My major misconception was that a branch was like branch on a tree consisting of all the nodes back to the trunk instead of just a temporary pointer to a single commit/node. – JonN Dec 14 '16 at 02:12
  • So now I tried to create a bare Git repository so I can push to it. I'm still failing. I created the bare repository by cloning but not checking out any branch. The "bare" repository has nothing but a .git folder in it which seems to match what I have read about bare repositories. However when I try to push it gives me the error *fatal: 'P:\Bitworks\Projects\Alcatraz\Vehicle' does not appear to be a git repository fatal: Could not read from remote repository.* – JonN Dec 17 '16 at 23:16
  • You would normally create a bare repository with `git clone --bare `, which otherwise is the same as any other `git clone`. There will be no `.git` within this clone, it will just have the files and directories that would appear *in* `.git` in a normal (non-bare) clone. (It is possible to convert a non-bare clone to bare, though, by configuring `core.bare` to `true` and moving everything up one directory and then removing the empty `.git` directory.) – torek Dec 17 '16 at 23:19
  • With a little more research I see that SourceTree (which I was trying to use) does not support creation of of bare repos. i have to use command line to do it. Where do I find this `core.bare` to be set to `true` ? Is it in some init file somewhere? – JonN Dec 17 '16 at 23:38
  • All of Git's configuration variables are set or accessible via `git config`. The actual local variable is in `.git/config` (or for a bare repository, `config`). There are three locations for configuration: "system" (all users on the machine), "global" (all YOUR repositories on the machine), and local (this one repository). Obviously `core.bare` should not be made global or system-wide. :-) – torek Dec 18 '16 at 00:00
  • Ok so this continues to frustrate. I deleted my remote repository on local network drive. Then created a bare repository with command line P: cd Bitworks\Projects\Alcatraz\Vehicle git clone --bare C:\BW\Alcatraz\Vehicle The Vehicle folder now contains a folder called Vehicle.git Then I add P:\Bitworks\Projects\Alcatraz\Vehicle as a remote in SourceTree Trying to push I get error that the remote is not a git repository. I also tried specifying the Vehicle.git folder with same results. "P:\Bitworks\Alcatraz\Vehicle\Vehicle.git does not appear to be a git repository" – JonN Dec 23 '16 at 22:17
  • I saw something in another question that suggests that network drives don't work right unless you use `file://` prefixes or some such. (This might have been a Mercurial question, even.) I don't "do" Windows so I'm not sure what the real issue is, but it's specific to Windows, this stuff all just works on Linux and Mac and so on. – torek Dec 23 '16 at 23:07
  • I changed to using UNC reference to the file server as follows: `//bw1/Public/Bitworks/Projects/Alcatraz/Vehicle.git` instead of using drive letter and that worked. Using **file:** did not seem to work. – JonN Dec 26 '16 at 23:06
  • Now the issue is SourceTree apparently won't display the graph for a bare repository? Is there a way to see the graph for a bare repository? – JonN Dec 26 '16 at 23:12
  • Don't know about SourceTree, but console Git commands work just fine on bare repositories. If all else fails, though, just use a regular (non-bare) repo that fetches from the bare one, and fetch before looking at it. – torek Dec 26 '16 at 23:23
  • Well if I was willing to always open the remote repo and fetch from working repo instead of trying to push to remote from working repo I wouldn't need the bare repository at all. I did find TortoiseGit does display bare repos. So I'm using that now. – JonN Dec 27 '16 at 01:41
  • Yes, fetch-only work flows are possible. Most people simply find a centralized push location more convenient. – torek Dec 27 '16 at 02:34
2

Git push does not modify the index.

MrTux
  • 32,350
  • 30
  • 109
  • 146
  • Agreed. You probably just want `git push`. While @torek's answer is technically excellent, I think the take-away you really want is that there is something you're not quite understanding and you just want to push. – Mort Dec 11 '16 at 02:13
0

While "Don't push to a repository that has humans working in it" in the other answer applies well to regular team collaboration, sometimes you need to distribute unfinished changes between private repositories and using bare repository at server is overkill for it. You can do it without having to deal with issues of pushing to current branch by pushing to a remote namespace:

push user@workstation:src/project 'refs/heads/*:refs/remotes/my-laptop/*'

so that when you return to the workstation, you can merge or cherry-pick changes from remote branches in the my-laptop/ namespace. It also indicates that that work has done in another repository, which should easier to navigate later.

max630
  • 8,762
  • 3
  • 30
  • 55