{"thread":{"id":"20394","subject":"[PATCH 2/2] Implement git-vcs-p4","startedAt":"2009-08-05T05:02:31Z","lastAt":"2009-08-05T18:45:51Z","messageCount":5,"participants":["Daniel Barkalow","Junio C Hamano","Alex Riesen"],"isPatch":true,"patchVersion":1,"patchTotal":2},"messages":[{"id":"119574","messageId":"alpine.LNX.2.00.0908050056580.2147@iabervon.org","threadId":"20394","inReplyTo":null,"subject":"[PATCH 2/2] Implement git-vcs-p4","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2009-08-05T05:02:31Z","receivedAt":"2009-08-05T05:02:31Z","isPatch":true,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"This is probably not particularly appropriate for mainline\napplication, and is somewhat buggy, not extensively tested, and\nincomplete, but is here as an example implementation of the API and in\ncase it is useful to other people.\n\nIt has two implementations of the interaction with the Perforce\nserver: one that uses the command-line client (and therefore makes a\nton of separate connections to the server) and one that uses the\n(closed source, vaguely licensed) C++ API. The former does not support\neverything used in push/submit correctly at this point.\n\nIt also adds support to the Makefile for building C++ object files and\nlinking with a C++ linker. It should be easy to omit entirely for\nbuilds that don't use p4, and it's at least somewhat out of the way.\n\nFetch features:\n\n - following integrations (with some guessing)\n - finding other branches of a codeline\n\nPush features (only with the C++ API):\n\n - works if you don't do anything at all complicated\n\nSigned-off-by: Daniel Barkalow <barkalow@iabervon.org>\n---\n Documentation/git-vcs-p4.txt |   47 ++\n Makefile                     |   23 +\n vcs-p4/p4client-api.cc       |  455 +++++++++++++++\n vcs-p4/p4client.c            |  158 ++++++\n vcs-p4/p4client.h            |   74 +++\n vcs-p4/vcs-p4.c              | 1250 ++++++++++++++++++++++++++++++++++++++++++\n vcs-p4/vcs-p4.h              |  135 +++++\n 7 files changed, 2142 insertions(+), 0 deletions(-)\n create mode 100644 Documentation/git-vcs-p4.txt\n create mode 100644 vcs-p4/p4client-api.cc\n create mode 100644 vcs-p4/p4client.c\n create mode 100644 vcs-p4/p4client.h\n create mode 100644 vcs-p4/vcs-p4.c\n create mode 100644 vcs-p4/vcs-p4.h\n\ndiff --git a/Documentation/git-vcs-p4.txt b/Documentation/git-vcs-p4.txt\nnew file mode 100644\nindex 0000000..61da8c1\n--- /dev/null\n+++ b/Documentation/git-vcs-p4.txt\n@@ -0,0 +1,47 @@\n+Config\n+------\n+\n+vcs-p4.port::\n+\tThe value to use for P4PORT\n+\n+vcs-p4.client::\n+\tThe value to use for P4CLIENT\n+\n+vcs-p4.codelineformat::\n+\tA regular expression to match valid codelines; a codeline is a\n+\tdirectory that contains exactly those files that belong to a\n+\tversion of a project. Importing history with integrations will\n+\tgenerally discover codelines not explicitly marked to be\n+\timported, found when a file in a known codeline, whose full\n+\tpath is therefore the codeline path plus a relative path, is\n+\tintegrated from a file with a name that ends with that\n+\trelative path. However, files will sometimes be integrated\n+\tfrom non-codelines (that is, from a directory that contains\n+\tunrelated files whose history should not be tracked), and this\n+\toption can be used to ignore some directories.\n+\n+\tNote that, properly, the history of the individual files from\n+\ta non-codeline which got integrated into a codeline should\n+\tcontribute but that this is not presently supported.\n+\n+vcs-p4.findbranches::\n+\tIf true, attempt to find branches of the codeline(s) specified\n+\tby looking for integrations out of these codelines.\n+\n+vcs-p4.ignorecodeline::\n+\tA perforce location which is a codeline, but is not relevant\n+\tto this project. This only applies to finding branches; a\n+\tcodeline containing ancestors of the current codelines is\n+\talways imported, although it won't be given as a remote head\n+\tif it is ignored.\n+\n+remotes.*.vcs::\n+\tThe string \"p4\" to use this importer.\n+\n+remotes.*.codeline::\n+\tThe perforce location of a codeline to track. Other codelines\n+\tmay be discovered by git-vcs-p4, but it will make no attempt\n+\tto get versions in these locations more recent than the last\n+\tversions that contribute at present to the tracked codelines,\n+\tand it will not make them available for matching in \"fetch\"\n+\tpatterns.\ndiff --git a/Makefile b/Makefile\nindex 0ebf9dd..638127a 100644\n--- a/Makefile\n+++ b/Makefile\n@@ -364,6 +364,7 @@ PROGRAMS += git-unpack-file$X\n PROGRAMS += git-update-server-info$X\n PROGRAMS += git-upload-pack$X\n PROGRAMS += git-var$X\n+PROGRAMS += git-remote-p4$X\n \n # List built-in command $C whose implementation cmd_$C() is not in\n # builtin-$C.o but is linked in as part of some other command.\n@@ -1252,6 +1253,7 @@ endif\n ifneq ($(findstring $(MAKEFLAGS),s),s)\n ifndef V\n \tQUIET_CC       = @echo '   ' CC $@;\n+\tQUIET_CXX      = @echo '   ' CXX $@;\n \tQUIET_AR       = @echo '   ' AR $@;\n \tQUIET_LINK     = @echo '   ' LINK $@;\n \tQUIET_BUILT_IN = @echo '   ' BUILTIN $@;\n@@ -1448,12 +1450,16 @@ git.o git.spec \\\n \t$(patsubst %.perl,%,$(SCRIPT_PERL)) \\\n \t: GIT-VERSION-FILE\n \n+vcs-p4/%.o: ALL_CFLAGS += -I.\n+\n %.o: %.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<\n %.s: %.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -S $(ALL_CFLAGS) $<\n %.o: %.S\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) $<\n+%.o: %.cc GIT-CFLAGS\n+\t$(QUIET_CXX)$(CXX) -o $*.o -c $(ALL_CFLAGS) $<\n \n exec_cmd.o: exec_cmd.c GIT-CFLAGS\n \t$(QUIET_CC)$(CC) -o $*.o -c $(ALL_CFLAGS) \\\n@@ -1498,6 +1504,22 @@ git-remote-http$X git-remote-https$X git-remote-ftp$X: remote-curl.o http.o http\n \t$(QUIET_LINK)$(CC) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n \t\t$(LIBS) $(CURL_LIBCURL) $(EXPAT_LIBEXPAT)\n \n+ifdef P4API_BASE\n+P4_IMPL=p4client-api\n+\n+vcs-p4/p4client-api.o: ALL_CFLAGS += -I$(P4API_BASE)/include\n+P4_LINK=$(CXX)\n+P4LIBS=-L$(P4API_BASE)/lib -lclient -lrpc -lsupp\n+else\n+P4_IMPL=p4client\n+P4_LINK=$(CC)\n+endif\n+\n+git-remote-p4$X: LIBS += $(P4LIBS)\n+git-remote-p4$X: vcs-p4/vcs-p4.o vcs-p4/$(P4_IMPL).o $(GITLIBS)\n+\t$(QUIET_LINK)$(P4_LINK) $(ALL_CFLAGS) -o $@ $(ALL_LDFLAGS) $(filter %.o,$^) \\\n+\t\t$(LIBS)\n+\n $(LIB_OBJS) $(BUILTIN_OBJS): $(LIB_H)\n $(patsubst git-%$X,%.o,$(PROGRAMS)) git.o: $(LIB_H) $(wildcard */*.h)\n builtin-revert.o wt-status.o: wt-status.h\n@@ -1759,6 +1781,7 @@ distclean: clean\n \n clean:\n \t$(RM) *.o mozilla-sha1/*.o arm/*.o ppc/*.o compat/*.o compat/*/*.o xdiff/*.o \\\n+\t\tvcs-p4/*.o \\\n \t\t$(LIB_FILE) $(XDIFF_LIB)\n \t$(RM) $(ALL_PROGRAMS) $(BUILT_INS) git$X\n \t$(RM) $(TEST_PROGRAMS)\ndiff --git a/vcs-p4/p4client-api.cc b/vcs-p4/p4client-api.cc\nnew file mode 100644\nindex 0000000..3ff5962\n--- /dev/null\n+++ b/vcs-p4/p4client-api.cc\n@@ -0,0 +1,455 @@\n+extern \"C\" {\n+#include \"p4client.h\"\n+}\n+\n+#include <p4/clientapi.h>\n+\n+class VCSClientUser : public ClientUser {\n+  virtual void OutputInfo(char level, const char *data);\n+  virtual void OutputBinary(const char *data, int length);\n+  virtual void OutputText(const char *data, int length);\n+  // Stuff that shouldn't happen\n+  virtual void InputData(StrBuf *strbuf, Error *e);\n+  virtual void OutputError(const char *errBuf);\n+  virtual void OutputStat(StrDict *varList);\n+  virtual void Prompt(const StrPtr &msg, StrBuf &rsp, int noEcho, Error *e);\n+  virtual void ErrorPause(char *errBuf, Error *e);\n+  virtual void Edit(FileSys *f1, Error *e);\n+  virtual void Diff(FileSys *f1, FileSys *f2, int doPage,\n+\t\t    char *diffFlags, Error *e);\n+  virtual void Merge(FileSys *base, FileSys *leg1, FileSys *leg2,\n+\t\t     FileSys *result, Error *e);\n+  virtual int Resolve(ClientMerge *m, Error *e);\n+  virtual void Help(const char *const *help);\n+  virtual FileSys *File(FileSysType type);\n+\n+public:\n+  void *data;\n+  void (*info_cb)(void *, int, const char *);\n+  void (*form_cb)(void *, const char *, const char *);\n+  const char *(*form_io_cb)(void *, const char *, const char *);\n+\n+  void (*buffer_cb)(void *, const char *buffer, int length);\n+\n+  void clear() {\n+    info_cb = NULL;\n+    form_cb = NULL;\n+    form_io_cb = NULL;\n+    buffer_cb = NULL;\n+  }\n+\n+  VCSClientUser() {\n+    strbuf_init(&input, 0);\n+    tree = NULL;\n+  }\n+\n+  const unsigned char *tree;\n+  const char *base;\n+\n+private:\n+  struct strbuf input;\n+};\n+\n+class PhonyFileSys : public FileSys {\n+  virtual void Open(FileOpenMode mode, Error *e);\n+  virtual void Write(const char *buf, int len, Error *e);\n+  virtual int Read(char *buf, int len, Error *e);\n+  virtual void Close(Error *e);\n+  virtual int Stat();\n+  virtual int StatModTime();\n+  virtual void Truncate(Error *e);\n+  virtual void Unlink(Error *e);\n+  virtual void Rename(FileSys *target, Error *e);\n+  virtual void Chmod(FilePerm perms, Error *e);\n+  virtual void ChmodTime(Error *e);\n+public:\n+  PhonyFileSys(VCSClientUser *user, FileSysType type) {\n+    this->user = user;\n+    this->type = type;\n+  }\n+private:\n+  VCSClientUser *user;\n+  int mode;\n+  const char *buffer;\n+  unsigned long posn;\n+  unsigned long size;\n+};\n+\n+static ClientApi client;\n+static VCSClientUser ui;\n+\n+void p4_init(const char *const *env)\n+{\n+  Error e;\n+  StrBuf msg;\n+\n+  while (*env) {\n+    if (!strncmp(*env, \"P4PORT=\", 7))\n+      client.SetPort((*env) + 7);\n+    if (!strncmp(*env, \"P4CLIENT=\", 7))\n+      client.SetClient((*env) + 9);\n+    env++;\n+  }\n+\n+  client.Init(&e);\n+  if (e.Test()) {\n+    e.Fmt(&msg);\n+    fprintf(stderr, msg.Text());\n+    exit(1);\n+  }\n+}\n+\n+void VCSClientUser::OutputBinary(const char *buffer, int length)\n+{\n+  if (buffer_cb) {\n+    buffer_cb(data, buffer, length);\n+  } else\n+    fprintf(stderr, \"Unexpected binary of length %d\\n\", length);\n+}\n+\n+void VCSClientUser::OutputText(const char *buffer, int length)\n+{\n+  if (buffer_cb) {\n+    buffer_cb(data, buffer, length);\n+  } else\n+    fprintf(stderr, \"Unexpected text of length %d\\n\", length);\n+}\n+\n+void VCSClientUser::OutputInfo(char level, const char *line)\n+{\n+  if (info_cb)\n+    info_cb(data, level - '0', line);\n+  else if (form_cb || form_io_cb) {\n+    struct strbuf key;\n+    struct strbuf value;\n+\n+    strbuf_init(&key, 0);\n+    strbuf_init(&value, 0);\n+\n+    const char *eol = NULL;\n+    for (; *line; line = eol + 1) {\n+      const char *eok;\n+\n+      eol = strchr(line, '\\n');\n+      if (!eol)\n+\tbreak;\n+      if (eol == line || line[0] == '#')\n+\tcontinue;\n+      eok = strchr(line, ':');\n+      if (!eok)\n+\tcontinue;\n+      strbuf_reset(&key);\n+      strbuf_reset(&value);\n+      strbuf_add(&key, line, eok - line);\n+      if (eok[1] == '\\t') {\n+\tstrbuf_add(&value, eok + 2, eol - (eok + 2));\n+      } else if (eok[1] == '\\n') {\n+\tfor (line = eol + 1; *line && line[0] != '\\n'; line = eol + 1) {\n+\t  eol = strchr(line, '\\n');\n+\t  strbuf_add(&value, line + 1, eol - (line + 1) + 1);\n+\t}\n+      }\n+      if (form_cb)\n+\tform_cb(data, key.buf, value.buf);\n+      else {\n+\tconst char *new_value = form_io_cb(data, key.buf, value.buf);\n+\tif (new_value) {\n+\t  strbuf_addbuf(&input, &key);\n+\t  strbuf_addch(&input, ':');\n+\t  if (strchr(new_value, '\\n')) {\n+\t    const char *posn = new_value;\n+\t    while (posn) {\n+\t      const char *eol = strchr(posn, '\\n');\n+\t      strbuf_addstr(&input, \"\\n\\t\");\n+\t      if (eol) {\n+\t\tstrbuf_add(&input, posn, eol - posn);\n+\t\tposn = eol + 1;\n+\t      } else {\n+\t\tstrbuf_addstr(&input, posn);\n+\t\tbreak;\n+\t      }\n+\t    }\n+\t  } else {\n+\t    strbuf_addch(&input, ' ');\n+\t    strbuf_addstr(&input, new_value);\n+\t  }\n+\t  strbuf_addch(&input, '\\n');\n+\t}\n+      }\n+    }\n+    strbuf_release(&key);\n+    strbuf_release(&value);\n+  } else\n+    fprintf(stderr, \"Unexpected info: %c ... %s\\n\", level, line);\n+}\n+\n+void VCSClientUser::InputData(StrBuf *strbuf, Error *e)\n+{\n+  strbuf->Append(input.buf, input.len);\n+  strbuf_reset(&input);\n+  //fprintf(stderr, \"Unexpected input data\\n\");\n+}\n+\n+void VCSClientUser::OutputError(const char *errBuf)\n+{\n+  fprintf(stderr, \"Error output: %s\\n\", errBuf);\n+}\n+\n+void VCSClientUser::OutputStat(StrDict *varList)\n+{\n+  fprintf(stderr, \"Unexpected stat\\n\");\n+}\n+\n+void VCSClientUser::Prompt(const StrPtr &msg, StrBuf &rsp,\n+\t\t\t   int noEcho, Error *e)\n+{\n+  fprintf(stderr, \"Prompt\\n\");\n+}\n+\n+void VCSClientUser::ErrorPause(char *errBuf, Error *e)\n+{\n+  fprintf(stderr, \"Error pause from p4\\n\");\n+}\n+\n+void VCSClientUser::Edit(FileSys *f1, Error *e)\n+{\n+  fprintf(stderr, \"Edit request from p4\\n\");\n+}\n+\n+void VCSClientUser::Diff(FileSys *f1, FileSys *f2, int doPage,\n+\t\t\t char *diffFlags, Error *e)\n+{\n+  fprintf(stderr, \"Diff from p4\\n\");\n+}\n+\n+void VCSClientUser::Merge(FileSys *base, FileSys *leg1, FileSys *leg2,\n+\t\t\t  FileSys *result, Error *e)\n+{\n+  fprintf(stderr, \"Merge from p4\\n\");\n+}\n+\n+int VCSClientUser::Resolve(ClientMerge *m, Error *e)\n+{\n+  fprintf(stderr, \"Resolve from p4\\n\");\n+  m->Select(CMS_MERGED, e);\n+  return CMS_MERGED;\n+}\n+\n+void VCSClientUser::Help(const char *const *help)\n+{\n+  fprintf(stderr, \"Help from p4\\n\");\n+}\n+\n+FileSys *VCSClientUser::File(FileSysType type)\n+{\n+  FileSys *ret;\n+  fprintf(stderr, \"File from p4: %d\\n\", type);\n+  ret = new PhonyFileSys(this, type);\n+  return ret;\n+}\n+\n+void PhonyFileSys::Open(FileOpenMode mode, Error *e)\n+{\n+  const char *openpath = path.Text();\n+  fprintf(stderr, \"Open %s for %d\\n\", openpath, mode);\n+  if (mode == FOM_READ) {\n+    if (strncmp(openpath, user->base, strlen(user->base))) {\n+      e->Sys(\"Path outside of working directory\", openpath);\n+      return;\n+    }\n+    openpath = openpath + strlen(user->base);\n+    if (openpath[0] == '/')\n+      openpath++;\n+    unsigned filemode;\n+    if (!user->tree)\n+      e->Sys(\"Unable to read file\", openpath);\n+    buffer = get_tree_path(user->tree, openpath, &filemode, &size);\n+    if (!buffer)\n+      e->Sys(\"File not found\", openpath);\n+    posn = 0;\n+  }\n+}\n+\n+void PhonyFileSys::Write(const char *buf, int len, Error *e)\n+{\n+}\n+\n+int PhonyFileSys::Read(char *buf, int len, Error *e)\n+{\n+  if (len + posn > size)\n+    len = size - posn;\n+  memcpy(buf, buffer + posn, len);\n+  posn += len;\n+  return len;\n+}\n+\n+void PhonyFileSys::Close(Error *e)\n+{\n+}\n+\n+int PhonyFileSys::Stat()\n+{\n+  const char *openpath = path.Text();\n+  unsigned filemode;\n+  fprintf(stderr, \"Stat %s\\n\", openpath);\n+  if (!user->tree)\n+    return 0;\n+  if (strncmp(openpath, user->base, strlen(user->base))) {\n+    return 0;\n+  }\n+  openpath = openpath + strlen(user->base);\n+  if (openpath[0] == '/')\n+    openpath++;\n+  buffer = get_tree_path(user->tree, openpath, &filemode, &size);\n+  if (!buffer)\n+    return 0;\n+  fprintf(stderr, \"Exists\\n\");\n+  return FSF_EXISTS;\n+}\n+\n+int PhonyFileSys::StatModTime()\n+{\n+  const char *openpath = path.Text();\n+  fprintf(stderr, \"Stat modtime %s\\n\", openpath);\n+  return 0;\n+}\n+\n+void PhonyFileSys::Truncate(Error *e)\n+{\n+  fprintf(stderr, \"truncate\\n\");\n+}\n+\n+void PhonyFileSys::Unlink(Error *e)\n+{\n+  fprintf(stderr, \"unlink\\n\");\n+}\n+\n+void PhonyFileSys::Rename(FileSys *target, Error *e)\n+{\n+  fprintf(stderr, \"rename %s to %s\\n\", path.Text(), target->Path()->Text());\n+}\n+\n+void PhonyFileSys::Chmod(FilePerm perms, Error *e)\n+{\n+  fprintf(stderr, \"chmod\\n\");\n+}\n+\n+void PhonyFileSys::ChmodTime(Error *e)\n+{\n+  fprintf(stderr, \"chmodtime\\n\");\n+}\n+\n+int p4_call(int fds[], const char *arg0, int argc, char *const *argv)\n+{\n+  ui.data = NULL;\n+  ui.clear();\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  p4_fini();\n+  exit(1);\n+  return 0;\n+}\n+\n+int _p4_call_unknown(const char *arg0, int argc, char *const *argv)\n+{\n+  ui.clear();\n+  ui.data = NULL;\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int _p4_call_info(const char *arg0, int argc, char *const *argv,\n+\t\t  void *data,\n+\t\t  void (*cb)(void *data, int level, const char *line))\n+{\n+  ui.clear();\n+  ui.data = data;\n+  ui.info_cb = cb;\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int _p4_call_form(const char *arg0, int argc, char *const *argv,\n+\t\t  void *data,\n+\t\t  void (*cb)(void *data, const char *key, const char *value))\n+{\n+  char **new_argv = (char **)calloc(argc + 2, sizeof(*new_argv));\n+  int i;\n+  new_argv[0] = \"-o\";\n+  for (i = 0; i < argc; i++)\n+    new_argv[i + 1] = argv[i];\n+  new_argv[argc + 1] = NULL;\n+  ui.clear();\n+  ui.data = data;\n+  ui.form_cb = cb;\n+  client.SetArgv(argc + 1, new_argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int _p4_call_form_io(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t     const char *(*cb)(void *data, const char *key, const char *value))\n+{\n+  char **new_argv = (char **)calloc(argc + 2, sizeof(*new_argv));\n+  int i;\n+  new_argv[0] = \"-o\";\n+  for (i = 0; i < argc; i++)\n+    new_argv[i + 1] = argv[i];\n+  new_argv[argc + 1] = NULL;\n+  ui.clear();\n+  ui.data = data;\n+  ui.form_io_cb = cb;\n+  client.SetArgv(argc + 1, new_argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+int _p4_call_buffer(const char *arg0, int argc, char *const *argv,\n+\t\t    void *data,\n+\t\t    void (*cb)(void *data, const char *buffer, int length))\n+{\n+  ui.clear();\n+  ui.data = data;\n+  ui.buffer_cb = cb;\n+  client.SetArgv(argc, argv);\n+  client.Run(arg0, &ui);\n+  return 0;\n+}\n+\n+void p4_write_blob(const char *base, unsigned mode, const unsigned char *sha1,\n+\t\t   const char *path)\n+{\n+}\n+\n+void p4_write_tree(const char *base, const unsigned char *sha1)\n+{\n+  ui.base = base;\n+  ui.tree = sha1;\n+}\n+\n+void p4_release_tree(void)\n+{\n+  ui.base = NULL;\n+  ui.tree = NULL;\n+}\n+\n+int p4_complete()\n+{\n+  return 0;\n+}\n+\n+int p4_fini()\n+{\n+  Error e;\n+  StrBuf msg;\n+\n+  client.Final(&e);\n+  if (e.Test()) {\n+    e.Fmt(&msg);\n+    fprintf(stderr, msg.Text());\n+    exit(1);\n+  }\n+  return 0;\n+}\ndiff --git a/vcs-p4/p4client.c b/vcs-p4/p4client.c\nnew file mode 100644\nindex 0000000..44f21da\n--- /dev/null\n+++ b/vcs-p4/p4client.c\n@@ -0,0 +1,158 @@\n+#include \"p4client.h\"\n+\n+#include \"cache.h\"\n+#include \"run-command.h\"\n+\n+static const char *const *envp;\n+\n+void p4_init(const char *const *env)\n+{\n+\tenvp = env;\n+}\n+\n+static struct child_process child;\n+\n+int p4_call(int fds[], const char *arg0, int argc, char *const *argv)\n+{\n+\tint i;\n+\tmemset(&child, 0, sizeof(child));\n+\tif (fds) {\n+\t\tchild.in = -1;\n+\t\tchild.out = -1;\n+\t} else {\n+\t\tchild.no_stdin = 1;\n+\t\tchild.no_stdout = 1;\n+\t}\n+\tchild.err = 0;\n+\tchild.argv = xcalloc(argc + 3, sizeof(*argv));\n+\tchild.argv[0] = \"p4\";\n+\tchild.argv[1] = arg0;\n+\tchild.env = envp;\n+\tfor (i = 0; i < argc; i++)\n+\t\tchild.argv[i + 2] = argv[i];\n+\tchild.argv[argc + 2] = NULL;\n+\tstart_command(&child);\n+\tif (fds) {\n+\t\tfds[0] = child.in;\n+\t\tfds[1] = child.out;\n+\t}\n+\treturn 0;\n+}\n+\n+int _p4_call_info(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, int level, const char *line))\n+{\n+\tint fds[2];\n+\tstruct strbuf line;\n+\tFILE *input;\n+\n+\tif (p4_call(fds, arg0, argc, argv))\n+\t\treturn -1;\n+\n+\tstrbuf_init(&line, 0);\n+\tinput = fdopen(fds[1], \"r\");\n+\twhile (!strbuf_getline(&line, input, '\\n')) {\n+\t\tint level = 0;\n+\t\tchar *posn = line.buf;\n+\t\twhile (!prefixcmp(posn, \"... \")) {\n+\t\t\tposn += 4;\n+\t\t\tlevel++;\n+\t\t}\n+\t\tcb(data, level, posn);\n+\t}\n+\tp4_complete();\n+\treturn 0;\n+}\n+\n+int _p4_call_form(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, const char *key, const char *value))\n+{\n+\tint fds[2];\n+\tstruct strbuf line;\n+\tstruct strbuf key;\n+\tstruct strbuf value;\n+\tFILE *input;\n+\n+\tif (p4_call(fds, arg0, argc, argv))\n+\t\treturn -1;\n+\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_init(&key, 0);\n+\tstrbuf_init(&value, 0);\n+\tinput = fdopen(fds[1], \"r\");\n+\tfor (; !strbuf_getline(&line, input, '\\n'); strbuf_reset(&line)) {\n+\t\tconst char *eok;\n+\n+\t\tif (!line.buf[0] || line.buf[0] == '#')\n+\t\t\tcontinue;\n+\t\teok = strchr(line.buf, ':');\n+\t\tif (!eok)\n+\t\t\tcontinue;\n+\t\tstrbuf_reset(&key);\n+\t\tstrbuf_reset(&value);\n+\t\tstrbuf_add(&key, line.buf, eok - line.buf);\n+\t\tif (eok[1] == '\\t')\n+\t\t\tstrbuf_addstr(&value, eok + 2);\n+\t\telse {\n+\t\t\tstrbuf_reset(&line);\n+\t\t\twhile (!strbuf_getline(&line, input, '\\n') && line.len) {\n+\t\t\t\tstrbuf_addstr(&value, line.buf + 1);\n+\t\t\t\tstrbuf_addch(&value, '\\n');\n+\t\t\t\tstrbuf_reset(&line);\n+\t\t\t}\n+\t\t}\n+\t\tcb(data, key.buf, value.buf);\n+\t}\n+\tp4_complete();\n+\treturn 0;\n+}\n+\n+int _p4_call_buffer(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t    void (*cb)(void *data, const char *buffer, int len))\n+{\n+\tint fds[2];\n+\tstruct strbuf block;\n+\tif (p4_call(fds, arg0, argc, argv))\n+\t\treturn -1;\n+\tstrbuf_init(&block, 0);\n+\tstrbuf_read(&block, fds[1], 0);\n+\tcb(data, block.buf, block.len);\n+\tp4_complete();\n+\treturn 0;\n+}\n+\n+\n+void p4_write_blob(const char *base, unsigned mode, const unsigned char *sha1,\n+\t\t   const char *path)\n+{\n+\tstruct strbuf buf;\n+\tvoid *content;\n+\tenum object_type type;\n+\tunsigned long size;\n+\tint fd;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/%s\", base, path);\n+\tcontent = read_sha1_file(sha1, &type, &size);\n+\tfd = open(buf.buf, O_WRONLY | O_CREAT, (mode & 0100) ? 0666 : 0777);\n+\tif (fd < 0) {\n+\t\tdie(\"Got err %d\", errno);\n+\t}\n+\twrite_or_die(fd, content, size);\n+\treturn 0;\n+}\n+\n+int p4_complete(void)\n+{\n+\tif (!child.no_stdin)\n+\t\tclose(child.in);\n+\tif (!child.no_stdout)\n+\t\tclose(child.out);\n+\tfinish_command(&child);\n+\treturn 0;\n+}\n+\n+int p4_fini(void)\n+{\n+\treturn 0;\n+}\ndiff --git a/vcs-p4/p4client.h b/vcs-p4/p4client.h\nnew file mode 100644\nindex 0000000..f0d0ded\n--- /dev/null\n+++ b/vcs-p4/p4client.h\n@@ -0,0 +1,74 @@\n+#ifndef P4CLIENT_H\n+#define P4CLIENT_H\n+\n+#include <stdlib.h>\n+#include <stdio.h>\n+#include <string.h>\n+#include \"strbuf.h\"\n+\n+/**\n+ * buffer: print\n+ * form: change\n+ * info: filelog, where, sync\n+ **/\n+\n+void p4_init(const char *const *env);\n+\n+int p4_call(int fds[], const char *arg0, int argc, char *const *argv);\n+\n+/** Calls back with a bunch of (data, level, line) **/\n+#define p4_call_info(arg0, argc, argv, data, cb) \\\n+\t(0 ? ((*(cb))((data), 0, NULL), 1) : \\\n+\t _p4_call_info(arg0, argc, argv, (void *)data, (void (*)(void *, int, const char *)) cb))\n+\n+/** Calls back with a bunch of (data, key, value); implies \"-o\" **/\n+#define p4_call_form(arg0, argc, argv, data, cb) \\\n+\t(0 ? ((*(cb))((data), NULL, NULL), 1) : \\\n+\t _p4_call_form(arg0, argc, argv, (void *)data, (void (*)(void *, const char *, const char *)) cb))\n+\n+/** Calls back with a bunch of (data, key, value); implies \"-o\", and\n+ * is followed by sending back form data with \"-i\" automatically.\n+ **/\n+#define p4_call_form_io(arg0, argc, argv, data, cb)\t\t\\\n+\t(0 ? ({ __attribute__((unused)) const char *r = (*(cb))((data), NULL, NULL); 1;}) : \\\n+\t _p4_call_form_io(arg0, argc, argv, (void *)data, (const char *(*)(void *, const char *, const char *)) cb))\n+\n+/** Calls back with a bunch of (data, buffer, len) **/\n+#define p4_call_buffer(arg0, argc, argv, data, cb) \\\n+\t(0 ?  ((*(cb))((data), NULL, 0), 1) : \\\n+\t _p4_call_buffer(arg0, argc, argv, (void *)data, (void (*)(void *, const char *, int)) cb))\n+\n+#define p4_call_unknown(arg0, argc, argv) _p4_call_unknown(arg0, argc, argv)\n+\n+/** One or the other of p4_write_tree and p4_write_blob will\n+ * actually be effective.\n+ **/\n+void p4_write_tree(const char *base, const unsigned char *sha1);\n+\n+const char *get_tree_path(const unsigned char *sha1, const char *path,\n+\t\t\t  unsigned *mode, unsigned long *size);\n+\n+void p4_write_blob(const char *base, unsigned mode, const unsigned char *sha1,\n+\t\t   const char *path);\n+\n+void p4_release_tree(void);\n+\n+int p4_complete();\n+\n+int p4_fini();\n+\n+int _p4_call_info(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, int level, const char *line));\n+\n+int _p4_call_form(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t  void (*cb)(void *data, const char *key, const char *value));\n+\n+int _p4_call_form_io(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t     const char *(*cb)(void *data, const char *key, const char *value));\n+\n+int _p4_call_buffer(const char *arg0, int argc, char *const *argv, void *data,\n+\t\t    void (*cb)(void *data, const char *buffer, int len));\n+\n+int _p4_call_unknown(const char *arg0, int argc, char *const *argv);\n+\n+#endif\ndiff --git a/vcs-p4/vcs-p4.c b/vcs-p4/vcs-p4.c\nnew file mode 100644\nindex 0000000..1b9147c\n--- /dev/null\n+++ b/vcs-p4/vcs-p4.c\n@@ -0,0 +1,1250 @@\n+#include \"cache.h\"\n+#include \"vcs-p4.h\"\n+#include \"strbuf.h\"\n+#include \"remote.h\"\n+#include \"commit.h\"\n+#include \"tree.h\"\n+#include \"tree-walk.h\"\n+#include \"diff.h\"\n+\n+#include \"p4client.h\"\n+\n+#include <string.h>\n+\n+/** Should we try to find codelines that branch off of the relevant\n+ * ones, for future reference? This lets us find new things in\n+ * ls-remote without making the user tell us.\n+ **/\n+static int find_new_codelines;\n+\n+static int ignore_codeline_nr;\n+static int ignore_codeline_alloc;\n+static char **ignore_codelines;\n+\n+static int prints_done = 0;\n+\n+static regex_t *codeline_regex;\n+\n+#define CODELINE_TAG \"Codeline: \"\n+#define CHANGE_TAG \"Changelist: \"\n+\n+#define LIST_P4_OPERATIONS 0\n+\n+/** List functions **/\n+\n+static void add_to_revision_list(struct p4_revision_list **list,\n+\t\t\t\t struct p4_revision *revision)\n+{\n+\twhile (*list)\n+\t\tlist = &(*list)->next;\n+\t*list = xcalloc(1, sizeof(**list));\n+\t(*list)->revision = revision;\n+}\n+\n+static struct p4_revision_list *copy_revision_list(struct p4_revision_list *lst)\n+{\n+\tstruct p4_revision_list *ret, **posn = &ret;\n+\twhile (lst) {\n+\t\t*posn = xcalloc(1, sizeof(**posn));\n+\t\t(*posn)->revision = lst->revision;\n+\t\tposn = &((*posn)->next);\n+\t\tlst = lst->next;\n+\t}\n+\treturn ret;\n+}\n+\n+/** Functions to find or create representations **/\n+\n+static struct p4_depot *get_depot(void)\n+{\n+\tstruct p4_depot *depot = xcalloc(1, sizeof(*depot));\n+\tdepot->next_mark = 1;\n+\treturn depot;\n+}\n+\n+static void add_mapped_changeset(struct p4_depot *depot, struct commit *commit,\n+\t\t\t\t struct p4_changeset *change)\n+{\n+\tchange->original = commit;\n+\tALLOC_GROW(depot->added, depot->added_nr + 1, depot->added_alloc);\n+\tdepot->added[depot->added_nr++] = change;\n+}\n+\n+static struct p4_changeset *get_changeset(struct p4_codeline *codeline,\n+\t\t\t\t\t  long number);\n+\n+static char *codeline_to_refname(const char *path) {\n+\tstruct strbuf buf;\n+\tif (prefixcmp(path, \"//\"))\n+\t\treturn NULL;\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"refs/p4/%s\", path + 2);\n+\treturn strbuf_detach(&buf, NULL);\n+}\n+\n+static char *refname_to_codeline(const char *refname) {\n+\tstruct strbuf buf;\n+\tif (prefixcmp(refname, \"refs/p4/\"))\n+\t\treturn NULL;\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"//%s\", refname + strlen(\"refs/p4/\"));\n+\treturn strbuf_detach(&buf, NULL);\n+}\n+\n+static struct p4_codeline *get_codeline(struct p4_depot *depot, const char *path)\n+{\n+\tstruct p4_codeline **posn, *codeline;\n+\tint i;\n+\tunsigned char sha1[20];\n+\n+\tif (codeline_regex && regexec(codeline_regex, path, 0, NULL, 0))\n+\t\treturn NULL;\n+\n+\tfor (posn = &depot->codelines; *posn; posn = &(*posn)->next)\n+\t\tif (!strcmp(path, (*posn)->path))\n+\t\t\treturn *posn;\n+\tcodeline = xcalloc(1, sizeof(*codeline));\n+\tcodeline->depot = depot;\n+\tcodeline->path = xstrdup(path);\n+\n+\tfor (i = 0; i < ignore_codeline_nr; i++)\n+\t\tif (!strcmp(path, ignore_codelines[i]))\n+\t\t\tcodeline->ignore = 1;\n+\n+\tcodeline->refname = codeline_to_refname(path);\n+\tif (!get_sha1(codeline->refname, sha1)) {\n+\t\tstruct commit *commit = lookup_commit(sha1);\n+\t\tchar *field;\n+\t\tparse_commit(commit);\n+\t\tfield = strstr(commit->buffer, CHANGE_TAG);\n+\t\tif (!field) {\n+\t\t\tfprintf(stderr, \"Couldn't find changeset line in commit\\n\");\n+\t\t} else {\n+\t\t\tstruct p4_changeset *changeset;\n+\t\t\tcodeline->finished_changeset =\n+\t\t\t\tatoi(field + strlen(CHANGE_TAG));\n+\t\t\tchangeset = get_changeset(codeline, codeline->finished_changeset);\n+\t\t\tchangeset->commit = commit;\n+\t\t\tcodeline->history = changeset;\n+\t\t}\n+\t}\n+\t*posn = codeline;\n+\treturn codeline;\n+}\n+\n+static struct p4_codeline *find_codeline(struct p4_depot *depot, const char *path)\n+{\n+\tstruct p4_codeline **posn;\n+\tfor (posn = &depot->codelines; *posn; posn = &(*posn)->next)\n+\t\tif (!prefixcmp(path, (*posn)->path))\n+\t\t\treturn *posn;\n+\treturn NULL;\n+}\n+\n+/** Inserts the changeset at the right place in order for the codeline **/\n+static struct p4_changeset *get_changeset(struct p4_codeline *codeline,\n+\t\t\t\t\t  long number)\n+{\n+\tstruct p4_changeset **posn = &codeline->changesets;\n+\tstruct p4_changeset *changeset, *prev = NULL;\n+\twhile (*posn && (*posn)->number < number) {\n+\t\tprev = *posn;\n+\t\tposn = &(*posn)->next;\n+\t}\n+\tif (*posn && (*posn)->number == number)\n+\t\treturn *posn;\n+\t//printf(\"# add changeset %lu in %s\\n\", number, codeline->path);\n+\tchangeset = xcalloc(1, sizeof(*changeset));\n+\tchangeset->codeline = codeline;\n+\tchangeset->next = *posn;\n+\tchangeset->previous = prev;\n+\tif (changeset->next)\n+\t\tchangeset->next->previous = changeset;\n+\telse\n+\t\tcodeline->head = changeset;\n+\t*posn = changeset;\n+\tchangeset->number = number;\n+\tcodeline->num_changesets++;\n+\treturn changeset;\n+}\n+\n+static struct p4_changeset *changeset_from_commit(struct p4_depot *depot,\n+\t\t\t\t\t\t  struct commit *commit)\n+{\n+\tint i;\n+\tunsigned long number = 0;\n+\tchar *codeline = NULL, *field;\n+\tparse_commit(commit);\n+\tfield = strstr(commit->buffer, CHANGE_TAG);\n+\tif (field)\n+\t\tnumber = atoi(field + strlen(CHANGE_TAG));\n+\tfield = strstr(commit->buffer, CODELINE_TAG);\n+\tif (field) {\n+\t\tchar *end;\n+\t\tcodeline = field + strlen(CODELINE_TAG);\n+\t\tend = strchr(codeline, '\\n');\n+\t\tif (end)\n+\t\t\t*end = '\\0';\n+\t}\n+\tif (number && codeline)\n+\t\treturn get_changeset(get_codeline(depot, codeline), number);\n+\tfor (i = 0; i < depot->added_nr; i++) {\n+\t\tif (depot->added[i]->original == commit)\n+\t\t\treturn depot->added[i];\n+\t}\n+\treturn NULL;\n+}\n+\n+static struct p4_file *get_file_by_full(struct p4_codeline *codeline,\n+\t\t\t\t\tconst char *fullpath)\n+{\n+\tconst char *rel = fullpath + strlen(codeline->path);\n+\tstruct p4_file **posn;\n+\tfor (posn = &codeline->files; *posn; posn = &(*posn)->next) {\n+\t\tif (!strcmp((*posn)->name, rel))\n+\t\t\treturn *posn;\n+\t}\n+\t*posn = xcalloc(1, sizeof(**posn));\n+\t(*posn)->codeline = codeline;\n+\t(*posn)->name = xstrdup(rel);\n+\treturn *posn;\n+}\n+\n+static struct p4_file *get_related_file(struct p4_file *base, const char *path)\n+{\n+\tint basenamelen = strlen(base->name);\n+\tint reldirlen = strlen(path) - basenamelen;\n+\tstruct p4_codeline *codeline;\n+\tif (reldirlen > 0 && !strcmp(path + reldirlen, base->name)) {\n+\t\t/* File with the same name in another codeline */\n+\t\tchar *other = xstrndup(path, reldirlen);\n+\t\t//printf(\"# find %s in %s\\n\", path, other);\n+\t\tcodeline = get_codeline(base->codeline->depot, other);\n+\t\tfree(other);\n+\t\tif (codeline)\n+\t\t\treturn get_file_by_full(codeline, path);\n+\t\treturn NULL;\n+\t}\n+\tcodeline = find_codeline(base->codeline->depot, path);\n+\tif (codeline) {\n+\t\t/* File with a different name in some known codeline */\n+\t\treturn get_file_by_full(codeline, path);\n+\t}\n+\tfprintf(stderr, \"Failed to identify %s\\n\", path);\n+\t/* Not in any known codeline; need to recheck this after\n+\t * discovering codelines completes.\n+\t */\n+\treturn NULL;\n+}\n+\n+static struct p4_revision *get_revision(struct p4_file *file, unsigned number)\n+{\n+\tstruct p4_revision **posn;\n+\tstruct p4_revision *revision;\n+\tfor (posn = &file->revisions; *posn && (*posn)->number < number;\n+\t     posn = &(*posn)->next)\n+\t\t;\n+\tif (!*posn || (*posn)->number != number) {\n+\t\trevision = xcalloc(1, sizeof(*revision));\n+\t\trevision->next = *posn;\n+\t\t*posn = revision;\n+\t\trevision->number = number;\n+\t\trevision->file = file;\n+\t}\n+\treturn *posn;\n+}\n+\n+static int parse_p4_date(const char *date)\n+{\n+\tstruct tm tm;\n+\tmemset(&tm, 0, sizeof(tm));\n+\ttm.tm_year = strtol(date, NULL, 10) - 1900;\n+\ttm.tm_mon = strtol(date + 5, NULL, 10) - 1;\n+\ttm.tm_mday = strtol(date + 8, NULL, 10);\n+\ttm.tm_hour = strtol(date + 11, NULL, 10);\n+\ttm.tm_min = strtol(date + 14, NULL, 10);\n+\ttm.tm_sec = strtol(date + 17, NULL, 10);\n+\treturn mktime(&tm);\n+}\n+\n+static int is_keyword(const char *text, int keywords)\n+{\n+\tif (!prefixcmp(text, \"Id: \") ||\n+\t    !prefixcmp(text, \"Header: \"))\n+\t\treturn 1;\n+\tif (keywords == 1)\n+\t\treturn 0;\n+\treturn !prefixcmp(text, \"Date: \") ||\n+\t\t!prefixcmp(text, \"DateTime: \") ||\n+\t\t!prefixcmp(text, \"Change: \") ||\n+\t\t!prefixcmp(text, \"File: \") ||\n+\t\t!prefixcmp(text, \"Revision: \") ||\n+\t\t!prefixcmp(text, \"Author: \");\n+}\n+\n+static void handle_keywords(struct strbuf *buf, int keywords)\n+{\n+\tint posn = 0;\n+\tchar *keyword;\n+\n+\tif (!keywords)\n+\t\treturn;\n+\n+\tdo {\n+\t\tkeyword = strchr(buf->buf + posn, '$');\n+\t\tif (!keyword)\n+\t\t\tbreak;\n+\t\tif (!is_keyword(keyword + 1, keywords)) {\n+\t\t\tposn = keyword - buf->buf + 1;\n+\t\t\tcontinue;\n+\t\t}\n+\t\tchar *eok = strchr(keyword + 1, ':');\n+\t\tsize_t kwl = strcspn(eok, \"$\\n\");\n+\t\tif (!eok[kwl])\n+\t\t\tbreak;\n+\t\tif (eok[kwl] == '$') {\n+\t\t\tstrbuf_remove(buf, eok - buf->buf, kwl);\n+\t\t} else {\n+\t\t\tposn = eok - buf->buf + kwl + 1;\n+\t\t}\n+\t} while (1);\n+}\n+\n+static const char *get_file_type(const char *text, size_t len)\n+{\n+\tif (len == 5 && !memcmp(text, \"ktext\", 5))\n+\t\treturn \"text+k\";\n+\tif (len == 5 && !memcmp(text, \"xtext\", 5))\n+\t\treturn \"text+x\";\n+\tif (len == 6 && !memcmp(text, \"kxtext\", 6))\n+\t\treturn \"text+kx\";\n+\treturn xstrndup(text, len);\n+}\n+\n+static const char *get_file_mode(const char *type)\n+{\n+\tchar *p = strchr(type, '+');\n+\tif (!strcmp(type, \"symlink\"))\n+\t\treturn \"120000\";\n+\tif (p && strchr(p, 'x'))\n+\t\treturn \"100755\";\n+\treturn \"100644\";\n+}\n+\n+static int keywords(const char *type)\n+{\n+\tchar *p = strchr(type, '+');\n+\tif (p) {\n+\t\tchar *k = strchr(p, 'k');\n+\t\tif (k) {\n+\t\t\tif (k[1] == 'o')\n+\t\t\t\treturn 1;\n+\t\t\treturn 2;\n+\t\t}\n+\t}\n+\treturn 0;\n+}\n+\n+static void output_data(struct strbuf *buf)\n+{\n+\tprintf(\"data %d\\n\", buf->len);\n+\tfwrite(buf->buf, 1, buf->len, stdout);\n+\tprintf(\"\\n\");\n+}\n+\n+/** P4 operations **/\n+\n+static void set_working(struct p4_codeline *codeline, int level, const char *line)\n+{\n+\tchar *working = strrchr(line, ' ');\n+\tif (working)\n+\t\tcodeline->working = xstrdup(working + 1);\n+}\n+\n+static int p4_where(struct p4_codeline *codeline)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addstr(&buf, codeline->path);\n+\targv[0] = buf.buf;\n+\tp4_call_info(\"where\", 1, argv, codeline, set_working);\n+\treturn codeline->working ? 0 : -1;\n+}\n+\n+static void sync_cb(void *data, int level, const char *line)\n+{\n+\t//fprintf(stderr, \"%s\\n\", line);\n+}\n+\n+static void p4_sync(struct p4_changeset *changeset)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tprintf(\"progress syncing %s/...\\n\", changeset->codeline->working);\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/...@%lu\",\n+\t\t    changeset->codeline->working, changeset->codeline->head->number);\n+\targv[0] = buf.buf;\n+\tp4_call_info(\"sync\", 1, argv, NULL, sync_cb);\n+}\n+\n+static void p4_integrate(struct p4_codeline *codeline,\n+\t\t\t struct p4_changeset *side)\n+{\n+\tchar *argv[3];\n+\tstruct strbuf buf;\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/...@%lu\", side->codeline->path, side->number);\n+\targv[0] = \"-d\";\n+\targv[1] = strbuf_detach(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s/...\", codeline->path);\n+\targv[2] = buf.buf;\n+\tp4_call_unknown(\"integrate\", 3, argv);\n+\tfree(argv[1]);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void p4_resolve(void)\n+{\n+\tp4_call_unknown(\"resolve\", 0, NULL);\n+}\n+\n+static void p4_edit(struct p4_codeline *codeline, const char *path)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s%s\", codeline->working, path);\n+\targv[0] = buf.buf;\n+\tp4_call_unknown(\"edit\", 1, argv);\n+\tstrbuf_release(&buf);\n+}\n+\n+static void p4_add(struct p4_codeline *codeline, const char *path)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s%s\", codeline->working, path);\n+\targv[0] = buf.buf;\n+\tp4_call(NULL, \"add\", 1, argv);\n+\tstrbuf_release(&buf);\n+\tp4_complete();\n+}\n+\n+static void p4_delete(struct p4_codeline *codeline, const char *path)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf buf;\n+\n+\tstrbuf_init(&buf, 0);\n+\tstrbuf_addf(&buf, \"%s%s\", codeline->working, path);\n+\targv[0] = buf.buf;\n+\tp4_call(NULL, \"delete\", 1, argv);\n+\tstrbuf_release(&buf);\n+\tp4_complete();\n+}\n+\n+static const char *change_cb(struct commit *commit, const char *key, const char *value)\n+{\n+\tfprintf(stderr, \"Form for %s\\n\", key);\n+\tif (!strcmp(key, \"Description\")) {\n+\t\tconst char *message = strstr(commit->buffer, \"\\n\\n\");\n+\t\tif (message)\n+\t\t\tmessage += 2;\n+\t\tfprintf(stderr, \"Return %s\\n\", message);\n+\t\treturn message;\n+\t}\n+\treturn value;\n+}\n+\n+static void submit_cb(unsigned long *data, int level, const char *line)\n+{\n+\tint len = strlen(line);\n+\tif (!prefixcmp(line, \"Change \") && len > 18 &&\n+\t    !strncmp(line + len - strlen(\" submitted.\"), \" submitted.\",\n+\t\t     strlen(\" submitted.\")))\n+\t\t*data = atoi(line + 7);\n+}\n+\n+static unsigned long p4_submit(struct commit *commit)\n+{\n+\tunsigned long ret;\n+\tchar *argv[1];\n+\tp4_call_form_io(\"change\", 0, NULL, commit, change_cb);\n+\targv[0] = \"-i\";\n+\tp4_call_info(\"submit\", 1, argv, &ret, submit_cb);\n+\treturn ret;\n+}\n+\n+static void p4_print(struct p4_revision *revision)\n+{\n+\tchar *argv[2];\n+\tstruct strbuf line;\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_addf(&line, \"%s%s#%lu\",\n+\t\t    revision->file->codeline->path,\n+\t\t    revision->file->name, revision->number);\n+\targv[1] = strdup(line.buf);\n+\targv[0] = \"-q\";\n+\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"p4 print\\n\");\n+\n+\tstrbuf_reset(&line);\n+\n+\tp4_call_buffer(\"print\", 2, argv, &line, strbuf_add);\n+\n+\tfree(argv[1]);\n+\n+\thandle_keywords(&line, keywords(revision->type));\n+\n+\t/* Perforce puts a newline at the end when printing symlinks */\n+\tif (!strcmp(revision->type, \"symlink\"))\n+\t\tline.len--;\n+\n+\toutput_data(&line);\n+\n+\tstrbuf_release(&line);\n+\n+\tprints_done++;\n+}\n+\n+struct p4_change_data {\n+\tstruct p4_changeset *changeset;\n+\tint date;\n+\tchar *user;\n+\tstruct strbuf message;\n+};\n+\n+static void p4_change_cb(struct p4_change_data *data, const char *key,\n+\t\t\t const char *value)\n+{\n+\tif (!strcmp(key, \"User\"))\n+\t\tdata->user = xstrdup(value);\n+\telse if (!strcmp(key, \"Date\"))\n+\t\tdata->date = parse_p4_date(value);\n+\telse if (!strcmp(key, \"Description\"))\n+\t\tstrbuf_addstr(&data->message, value);\n+}\n+\n+static void p4_change(struct p4_changeset *changeset)\n+{\n+\tchar *argv[1];\n+\tstruct strbuf line;\n+\n+\tstruct p4_change_data data = {\n+\t\t.changeset = changeset,\n+\t\t.date = 0,\n+\t\t.user = NULL,\n+\t};\n+\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"p4 change\\n\");\n+\n+\tstrbuf_init(&data.message, 0);\n+\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_addf(&line, \"%lu\", changeset->number);\n+\targv[0] = line.buf;\n+\tp4_call_form(\"change\", 1, argv, &data, p4_change_cb);\n+\tstrbuf_release(&line);\n+\n+\tprintf(\"committer %s <%s> %d +0000\\n\",\n+\t       data.user, data.user, data.date);\n+\tfree(data.user);\n+\n+\tstrbuf_addf(&data.message,\n+\t\t    \"\\n\" CODELINE_TAG \"%s\\n\" CHANGE_TAG \"%lu\\n\",\n+\t\t    changeset->codeline->path, changeset->number);\n+\toutput_data(&data.message);\n+\tstrbuf_release(&data.message);\n+}\n+\n+struct p4_filelog_data {\n+\tstruct p4_codeline *codeline;\n+\tstruct p4_file *file;\n+\tstruct p4_revision *revision;\n+};\n+\n+static void p4_filelog_cb(struct p4_filelog_data *data,\n+\t\t\t  char level, const char *line)\n+{\n+\tif (level == 0) {\n+\t\tdata->file = get_file_by_full(data->codeline, line);\n+\t} else if (level == 1) {\n+\t\tint rev, change, delete = 0, branch = 0;\n+\t\tchar *posn;\n+\t\trev = strtoul(line + 1, &posn, 10);  /* skip the '#' */\n+\t\tposn += strlen(\" change \");\n+\t\tchange = strtoul(posn, &posn, 10);\n+\t\tif (!prefixcmp(posn, \" delete\"))\n+\t\t\tdelete = 1;\n+\t\tif (!prefixcmp(posn, \" branch\"))\n+\t\t\tbranch = 1;\n+\t\tposn = strchr(posn, '(') + 1;\n+\t\tdata->revision = get_revision(data->file, rev);\n+\t\tdata->revision->changeset =\n+\t\t\tget_changeset(data->codeline, change);\n+\t\tdata->revision->type = get_file_type(posn,\n+\t\t\t\t\t\t     strchr(posn, ')') - posn);\n+\t\tdata->revision->delete = delete;\n+\t\tdata->revision->branch = branch;\n+\t\tadd_to_revision_list(&data->revision->changeset->revisions,\n+\t\t\t\t     data->revision);\n+\t} else if (level == 2) {\n+\t\tconst char *path;\n+\t\tint rev, from = 0;\n+\t\tchar *type = xstrdup(line);\n+\t\tchar *posn = strrchr(type, ' ') + 1;\n+\n+\t\tfrom = (!prefixcmp(type, \"ignored\") &&\n+\t\t\tposn == type + strlen(\"ignored\") + 1) ||\n+\t\t\t!prefixcmp(strchr(type, ' '), \" from\");\n+\n+\t\tpath = posn;\n+\t\tposn = strchr(posn, '#');\n+\t\t*(posn++) = '\\0';\n+\t\tdo {\n+\t\t\t/* ???? What does a list of revisions mean? */\n+\t\t\trev = strtoul(posn, &posn, 10);\n+\t\t\tif (*posn != ',')\n+\t\t\t\tbreak;\n+\t\t\tposn += 2;\n+\t\t} while (1);\n+\t\tif (from) {\n+\t\t\tstruct p4_file *rel_file =\n+\t\t\t\tget_related_file(data->file, path);\n+\t\t\tif (!rel_file) {\n+\t\t\t\t/*\n+\t\t\t\tprintf(\"# Couldn't find %s related to %s %s\\n\",\n+\t\t\t\t       path, data->file->codeline->path,\n+\t\t\t\t       data->file->name);\n+\t\t\t\t*/\n+\t\t\t}\n+\t\t\tif (rel_file && rel_file->codeline != data->codeline)\n+\t\t\t\tadd_to_revision_list(&data->revision->integrated,\n+\t\t\t\t\t\t     get_revision(rel_file, rev));\n+\t\t} else if (find_new_codelines) {\n+\t\t\t/* This is an \"<op> into <path>#<rev>\" line.\n+\t\t\t * We just want to try to create a codeline.\n+\t\t\t */\n+\t\t\tget_related_file(data->file, path);\n+\t\t}\n+\t\tfree(type);\n+\t}\n+}\n+\n+/** Finds all files in the codeline, and all revisions of those files,\n+ * and all of the changesets they are from, and looks up the codelines\n+ * and files they integrate or branch.\n+ **/\n+static void p4_filelog(struct p4_codeline *codeline)\n+{\n+\tstruct strbuf line;\n+\n+\tstruct p4_filelog_data data = {\n+\t\t.codeline = codeline,\n+\t\t.file = NULL,\n+\t\t.revision = NULL\n+\t};\n+\tchar *arg;\n+\n+\tif (codeline->filelog_done)\n+\t\treturn;\n+\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"p4 filelog %s\\n\", codeline->path);\n+\n+\tstrbuf_init(&line, 0);\n+\tstrbuf_addstr(&line, codeline->path);\n+\tstrbuf_addstr(&line, \"/...\");\n+\targ = line.buf;\n+\tp4_call_info(\"filelog\", 1, &arg, &data, p4_filelog_cb);\n+\tstrbuf_release(&line);\n+\tif (codeline->history)\n+\t\tcodeline->unreported = codeline->history->next;\n+\telse\n+\t\tcodeline->unreported = codeline->changesets;\n+\tcodeline->filelog_done = 1;\n+}\n+\n+/** Functions to import things (i.e., fill out the representations) **/\n+\n+static struct p4_changeset_list *\n+find_codeline_changeset(struct p4_changeset_list **list,\n+\t\t\tstruct p4_codeline *codeline)\n+{\n+\twhile (*list) {\n+\t\tif ((*list)->changeset->codeline == codeline)\n+\t\t\treturn *list;\n+\t\tlist = &(*list)->next;\n+\t}\n+\t*list = xcalloc(1, sizeof(**list));\n+\treturn *list;\n+}\n+\n+static void resolve_codeline_contents(struct p4_codeline *codeline)\n+{\n+\tstruct p4_revision_list *prevrevs = NULL;\n+\tstruct p4_changeset *changeset = codeline->changesets;\n+\twhile (changeset) {\n+\t\tstruct p4_revision_list *changes =\n+\t\t\tcopy_revision_list(changeset->revisions);\n+\t\tchangeset->contents = changes;\n+\t\twhile (prevrevs) {\n+\t\t\tstruct p4_revision_list *posn;\n+\t\t\tint found = 0;\n+\t\t\tfor (posn = changes; posn; posn = posn->next) {\n+\t\t\t\tif (prevrevs->revision->file ==\n+\t\t\t\t    posn->revision->file) {\n+\t\t\t\t\tfound = 1;\n+\t\t\t\t\tbreak;\n+\t\t\t\t}\n+\t\t\t}\n+\t\t\tif (!found) {\n+\t\t\t\tstruct p4_revision_list *item =\n+\t\t\t\t\txcalloc(1, sizeof(*item));\n+\t\t\t\titem->revision = prevrevs->revision;\n+\t\t\t\titem->next = changeset->contents;\n+\t\t\t\tchangeset->contents = item;\n+\t\t\t}\n+\t\t\tprevrevs = prevrevs->next;\n+\t\t}\n+\n+\t\tprevrevs = changeset->contents;\n+\t\tchangeset = changeset->next;\n+\t}\n+}\n+\n+static void resolve_changeset_integrates(struct p4_changeset *changeset)\n+{\n+\tstruct p4_revision_list *posn;\n+\tstruct p4_changeset_list *changesets = NULL;\n+\t/* For each codeline, we want the highest numbered changeset\n+\t * that introduced a revision that has been integrated.\n+\t */\n+\tfor (posn = changeset->revisions; posn; posn = posn->next) {\n+\t\tstruct p4_revision_list *rev_ints = posn->revision->integrated;\n+\t\twhile (rev_ints) {\n+\t\t\tstruct p4_changeset_list *item;\n+\t\t\tif (rev_ints->revision->file->codeline == changeset->codeline) {\n+\t\t\t\trev_ints = rev_ints->next;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\t/* The revision doesn't have the changeset\n+\t\t\t * filled out unless we call this.\n+\t\t\t */\n+\t\t\tp4_filelog(rev_ints->revision->file->codeline);\n+\t\t\tif (!rev_ints->revision->changeset) {\n+\t\t\t\trev_ints = rev_ints->next;\n+\t\t\t\tcontinue;\n+\t\t\t}\n+\t\t\titem = find_codeline_changeset(&changesets,\n+\t\t\t\t\t\t       rev_ints->revision->file->codeline);\n+\t\t\tif (!item->changeset ||\n+\t\t\t    item->changeset->number < rev_ints->revision->changeset->number) {\n+\t\t\t\tif (0)\n+\t\t\t\t\tprintf(\"progress %lu integrates %s#%lu from %lu\\n\",\n+\t\t\t\t\t       changeset->number,\n+\t\t\t\t\t       rev_ints->revision->file->name,\n+\t\t\t\t\t       rev_ints->revision->number,\n+\t\t\t\t\t       rev_ints->revision->changeset->number);\n+\t\t\t\titem->changeset = rev_ints->revision->changeset;\n+\t\t\t}\n+\t\t\trev_ints = rev_ints->next;\n+\t\t}\n+\t}\n+\t/* We could issue a warning if the state of other files didn't\n+\t * match and yet didn't get integrated, but that's a lot of\n+\t * work and there's no good way to represent the case of a\n+\t * commit contributing to but not being completely obsoleted\n+\t * by another commit.\n+\t */\n+\tchangeset->integrated = changesets;\n+\twhile (changesets) {\n+\t\t//printf(\"# integrate %lu from %lu\\n\", changeset->number, changesets->changeset->number);\n+\t\tchangesets = changesets->next;\n+\t}\n+}\n+\n+static void follow_codeline(struct p4_codeline *target)\n+{\n+\tstruct p4_codeline *posn;\n+\tif (target->filelog_done)\n+\t\treturn;\n+\n+\tp4_filelog(target);\n+\n+\tif (0)\n+\t\tprintf(\"progress resolving integrates\\n\");\n+\n+\t/* Now resolve all the integrates in changesets */\n+\tfor (posn = target->depot->codelines; posn; posn = posn->next) {\n+\t\tstruct p4_changeset *changeset;\n+\t\tfor (changeset = posn->unreported; changeset; changeset = changeset->next) {\n+\t\t\tresolve_changeset_integrates(changeset);\n+\t\t}\n+\t\tresolve_codeline_contents(posn);\n+\t}\n+}\n+\n+static struct p4_codeline *import_depot(struct p4_depot *depot, const char *refname)\n+{\n+\tstruct p4_codeline *target;\n+\tchar *path = refname_to_codeline(refname);\n+\ttarget = get_codeline(depot, path);\n+\n+\tif (!target)\n+\t\tdie(\"Invalid codeline: %s\", path);\n+\n+\tfree(path);\n+\n+\tfollow_codeline(target);\n+\n+\treturn target;\n+}\n+\n+static void name_changeset(struct p4_changeset *changeset)\n+{\n+\tif (changeset->commit)\n+\t\tprintf(\"%s\\n\", sha1_to_hex(changeset->commit->object.sha1));\n+\telse\n+\t\tprintf(\":%d\\n\", changeset->mark);\n+}\n+\n+static void lookup_git_changeset(struct p4_codeline *codeline,\n+\t\t\t\t struct p4_changeset *changeset)\n+{\n+\twhile (!changeset->commit) {\n+\t\tstruct commit *parent = codeline->history->commit->parents->item;\n+\t\tparse_commit(parent);\n+\t\tcodeline->history->previous->commit = parent;\n+\t\tcodeline->history = codeline->history->previous;\n+\t}\n+}\n+\n+static void report_codeline(struct p4_codeline *codeline,\n+\t\t\t    struct p4_changeset *until);\n+\n+static void identify_changeset(struct p4_changeset *changeset)\n+{\n+\tif (changeset->mark || changeset->commit)\n+\t\treturn;\n+\tif (changeset->codeline->finished_changeset >= changeset->number)\n+\t\tlookup_git_changeset(changeset->codeline, changeset);\n+\telse\n+\t\treport_codeline(changeset->codeline, changeset);\n+}\n+\n+static int skip_found(struct p4_revision *revision,\n+\t\t      struct p4_revision_list **origin) {\n+\tstruct p4_revision *orev = NULL;\n+\tstruct p4_revision_list *i;\n+\twhile (*origin) {\n+\t\tif (!strcmp((*origin)->revision->file->name,\n+\t\t\t    revision->file->name)) {\n+\t\t\tstruct p4_revision_list *oitem = *origin;\n+\t\t\t*origin = oitem->next;\n+\t\t\torev = oitem->revision;\n+\t\t\tfree(oitem);\n+\t\t\tbreak;\n+\t\t} else {\n+\t\t\torigin = &((*origin)->next);\n+\t\t}\n+\t}\n+\tif (!revision->branch) /* It's changed anyway */\n+\t\treturn 0;\n+\tfor (i = revision->integrated; i; i = i->next) {\n+\t\tif (i->revision == orev)\n+\t\t\treturn 1;\n+\t}\n+\treturn 0;\n+}\n+\n+static void report_codeline(struct p4_codeline *codeline, struct p4_changeset *until)\n+{\n+\tstruct p4_changeset *changeset;\n+\tstruct p4_revision_list *rev;\n+\n+\tprintf(\"progress import codeline %s (%lu-%lu)\\n\", codeline->path,\n+\t       codeline->unreported->number, until->number);\n+\n+\tfor (changeset = codeline->unreported; changeset; changeset = changeset->next) {\n+\t\tstruct p4_changeset_list *integrated = changeset->integrated;\n+\t\tstruct p4_revision_list *origin = NULL;\n+\n+\t\twhile (integrated) {\n+\t\t\tidentify_changeset(integrated->changeset);\n+\t\t\tintegrated = integrated->next;\n+\t\t}\n+\t\tprintf(\"progress import changeset %lu (%s)\\n\",\n+\t\t       changeset->number, changeset->codeline->path);\n+\t\tprintf(\"# changeset %lu\\n\", changeset->number);\n+\t\tprintf(\"commit %s\\n\", codeline->refname);\n+\t\tchangeset->mark = codeline->depot->next_mark++;\n+\t\tprintf(\"mark :%d\\n\", changeset->mark);\n+\t\tp4_change(changeset);\n+\t\tintegrated = changeset->integrated;\n+\t\tif (changeset->previous) {\n+\t\t\tprintf(\"from \");\n+\t\t\tname_changeset(changeset->previous);\n+\t\t} else if (integrated) {\n+\t\t\tprintf(\"from \");\n+\t\t\torigin = copy_revision_list(integrated->changeset->contents);\n+\t\t\tname_changeset(integrated->changeset);\n+\t\t\tintegrated = integrated->next;\n+\t\t}\n+\n+\t\twhile (integrated) {\n+\t\t\tprintf(\"merge \");\n+\t\t\tname_changeset(integrated->changeset);\n+\t\t\tintegrated = integrated->next;\n+\t\t}\n+\n+\t\tfor (rev = changeset->revisions; rev; rev = rev->next) {\n+\t\t\tif (rev->revision->delete) {\n+\t\t\t\tprintf(\"D %s\\n\", rev->revision->file->name + 1);\n+\t\t\t} else if (!skip_found(rev->revision, &origin)) {\n+\t\t\t\tprintf(\"M %s inline %s\\n\",\n+\t\t\t\t       get_file_mode(rev->revision->type),\n+\t\t\t\t       rev->revision->file->name + 1);\n+\t\t\t\tp4_print(rev->revision);\n+\t\t\t}\n+\t\t}\n+\t\twhile (origin) {\n+\t\t\tstruct p4_revision_list *old;\n+\t\t\tprintf(\"D %s\\n\", origin->revision->file->name + 1);\n+\t\t\told = origin;\n+\t\t\torigin = origin->next;\n+\t\t\tfree(old);\n+\t\t}\n+\t\tprintf(\"\\n\");\n+\t\tcodeline->unreported = changeset->next;\n+\t\tif (changeset == until)\n+\t\t\tbreak;\n+\t}\n+\tprintf(\"checkpoint\\n\");\n+}\n+\n+static void export_change(struct diff_options *options,\n+\t\t\t  unsigned old_mode, unsigned new_mode,\n+\t\t\t  const unsigned char *old_sha1,\n+\t\t\t  const unsigned char *new_sha1,\n+\t\t\t  const char *path)\n+{\n+\tstruct p4_codeline *codeline = options->format_callback_data;\n+\tp4_edit(codeline, path);\n+\tp4_write_blob(codeline->working, new_mode, new_sha1, path);\n+}\n+\n+static void export_add_remove(struct diff_options *options,\n+\t\t\t      int addremove, unsigned mode,\n+\t\t\t      const unsigned char *sha1,\n+\t\t\t      const char *path)\n+{\n+\tstruct p4_codeline *codeline = options->format_callback_data;\n+\tif (addremove == '+') {\n+\t\tp4_write_blob(codeline->working, mode, sha1, path);\n+\t\tp4_add(codeline, path);\n+\t} else if (addremove == '-') {\n+\t\tp4_delete(codeline, path);\n+\t}\n+}\n+\n+static void export_p4(struct p4_depot *depot, unsigned char *sha1, const char *ref)\n+{\n+\tstruct p4_codeline *target;\n+\tstruct strbuf buf;\n+\n+\t// check client\n+\n+\tfprintf(stderr, \"Exporting %s to %s\\n\", sha1_to_hex(sha1), ref);\n+\n+\ttarget = import_depot(depot, ref);\n+\n+\tstrbuf_init(&buf, 0);\n+\n+\tstruct p4_changeset *parent = NULL, *integrate = NULL;\n+\tstruct commit *commit, *git_parent = NULL;\n+\tstruct commit_list *parents;\n+\tcommit = lookup_commit(sha1);\n+\tparse_commit(commit);\n+\tfor (parents = commit->parents; parents; parents = parents->next) {\n+\t\tstruct p4_changeset *p4_parent =\n+\t\t\tchangeset_from_commit(depot, parents->item);\n+\t\tif (p4_parent) {\n+\t\t\tif (p4_parent->codeline == target) {\n+\t\t\t\tparent = p4_parent;\n+\t\t\t\tgit_parent = parents->item;\n+\t\t\t} else\n+\t\t\t\tintegrate = p4_parent;\n+\t\t} else {\n+\t\t\tfprintf(stderr, \"Unknown parent\\n\");\n+\t\t\treturn;\n+\t\t}\n+\t}\n+\tif (target->head != parent) {\n+\t\tif (!parent) {\n+\t\t\tprintf(\"progress Couldn't find parent\\n\");\n+\t\t\treturn;\n+\t\t}\n+\t\tprintf(\"progress not up-to-date\\n\");\n+\t\treturn;\n+\t}\n+\tif (p4_where(target))\n+\t\treturn;\n+\tp4_sync(parent);\n+\n+\tif (!parent) {\n+\t\t// Need to start new codeline\n+\t\treturn;\n+\t}\n+\tp4_write_tree(target->working, commit->tree->object.sha1);\n+\tif (integrate) {\n+\t\tp4_integrate(target, integrate);\n+\t\tfprintf(stderr, \"Exporting merge\\n\");\n+\t\t// Dunno how to do this\n+\t\tp4_resolve();\n+\t\t//return;\n+\t}\n+\tstruct tree_desc pre, post;\n+\tstruct diff_options opts;\n+\tmemset(&opts, 0, sizeof(opts));\n+\tparse_tree(commit->tree);\n+\tparse_tree(git_parent->tree);\n+\tinit_tree_desc(&pre, git_parent->tree->buffer, git_parent->tree->size);\n+\tinit_tree_desc(&post, commit->tree->buffer, commit->tree->size);\n+\topts.change = export_change;\n+\topts.add_remove = export_add_remove;\n+\topts.format_callback_data = target;\n+\topts.flags = DIFF_OPT_RECURSIVE;\n+\tdiff_tree(&pre, &post, \"/\", &opts);\n+\n+\tunsigned long change = p4_submit(commit);\n+\n+\tp4_release_tree();\n+\n+\ttarget->filelog_done = 0;\n+\tfollow_codeline(target);\n+\tstruct p4_changeset *changeset = get_changeset(target, change);\n+\tadd_mapped_changeset(depot, commit, changeset);\n+\treport_codeline(target, changeset);\n+}\n+\n+static const char **env;\n+static int env_nr;\n+static int env_alloc;\n+\n+static int connected;\n+\n+static const char **codelines;\n+static int codeline_nr;\n+static int codeline_alloc;\n+\n+static int handle_config(const char *key, const char *value, void *cb)\n+{\n+\tstruct remote *remote = cb;\n+\tstruct strbuf buf;\n+\tconst char *subkey = NULL;\n+\n+\tif (!prefixcmp(key, \"vcs-p4.\"))\n+\t\tsubkey = key + strlen(\"vcs-p4.\");\n+\n+\tif (remote && !prefixcmp(key, \"remote.\") &&\n+\t    !prefixcmp(key + strlen(\"remote.\"), remote->name))\n+\t    subkey = key + strlen(\"remote.\") + strlen(remote->name) + 1;\n+\n+\tif (!subkey)\n+\t\treturn 0;\n+\n+\tif (!strcmp(subkey, \"findbranches\")) {\n+\t\tfind_new_codelines = git_config_bool(key, value);\n+\t}\n+\tif (!strcmp(subkey, \"ignorecodeline\")) {\n+\t\tALLOC_GROW(ignore_codelines, ignore_codeline_nr + 1,\n+\t\t\t   ignore_codeline_alloc);\n+\t\tignore_codelines[ignore_codeline_nr++] = xstrdup(value);\n+\t}\n+\tif (!strcmp(subkey, \"port\")) {\n+\t\tstrbuf_init(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"P4PORT=%s\", value);\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = strbuf_detach(&buf, NULL);\n+\t}\n+\tif (!strcmp(subkey, \"client\")) {\n+\t\tstrbuf_init(&buf, 0);\n+\t\tstrbuf_addf(&buf, \"P4CLIENT=%s\", value);\n+\n+\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\tenv[env_nr++] = strbuf_detach(&buf, NULL);\n+\t}\n+\tif (!strcmp(subkey, \"codelineformat\")) {\n+\t\tcodeline_regex = (regex_t*)xmalloc(sizeof(regex_t));\n+\t\tif (regcomp(codeline_regex, value, REG_EXTENDED)) {\n+\t\t\tfree(codeline_regex);\n+\t\t\tfprintf(stderr, \"Invalid codeline pattern: %s\",\n+\t\t\t\tvalue);\n+\t\t}\n+\t}\n+\tif (!strcmp(subkey, \"codeline\")) {\n+\t\tALLOC_GROW(codelines, codeline_nr + 1, codeline_alloc);\n+\t\tcodelines[codeline_nr++] = xstrdup(value);\n+\t}\n+\treturn 0;\n+}\n+\n+static void connect_to_p4(void)\n+{\n+\tif (!connected)\n+\t\tp4_init(env);\n+\tconnected = 1;\n+}\n+\n+static void disconnect_from_p4(void)\n+{\n+\tif (LIST_P4_OPERATIONS)\n+\t\tfprintf(stderr, \"Prints done: %d\\n\", prints_done);\n+\tif (connected)\n+\t\tp4_fini();\n+\tconnected = 0;\n+}\n+\n+const char *get_tree_path(const unsigned char *sha1, const char *path,\n+\t\t\t  unsigned *mode, unsigned long *size)\n+{\n+\tunsigned char blob_sha1[20];\n+\tenum object_type type;\n+\tif (get_tree_entry(sha1, path, blob_sha1, mode)) {\n+\t\terror(\"Couldn't find %s in %s\", path, sha1_to_hex(sha1));\n+\t\treturn NULL;\n+\t}\n+\treturn read_sha1_file(blob_sha1, &type, size);\n+}\n+\n+int main(int argc, const char **argv)\n+{\n+\tstruct remote *remote;\n+\tstruct strbuf buf;\n+\tstruct p4_depot *depot = NULL;\n+\n+\tsetup_git_directory();\n+\n+\tif (argc < 1) {\n+\t\tfprintf(stderr, \"Remote needed\");\n+\t\treturn 1;\n+\t}\n+\n+\tremote = remote_get(argv[1]);\n+\tgit_config(handle_config, remote);\n+\n+\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\tenv[env_nr++] = NULL;\n+\n+\tstrbuf_init(&buf, 0);\n+\tdo {\n+\t\tif (strbuf_getline(&buf, stdin, '\\n') == EOF)\n+\t\t\tbreak;\n+\t\tif (!*buf.buf)\n+\t\t\tbreak;\n+\t\tif (!strcmp(buf.buf, \"capabilities\")) {\n+\t\t\tprintf(\"import\\n\");\n+\t\t\tprintf(\"export\\n\");\n+\t\t\tprintf(\"\\n\");\n+\t\t\tfflush(stdout);\n+\t\t} else if (!prefixcmp(buf.buf, \"import \")) {\n+\t\t\tsave_commit_buffer = 1;\n+\n+\t\t\tfind_new_codelines = 0;\n+\n+\t\t\tconnect_to_p4();\n+\n+\t\t\tif (!depot)\n+\t\t\t\tdepot = get_depot();\n+\n+\t\t\tidentify_changeset(import_depot(depot, buf.buf + strlen(\"import \"))->head);\n+\t\t} else if (!strcmp(buf.buf, \"list\")) {\n+\t\t\tint i;\n+\n+\t\t\tgit_config(handle_config, remote);\n+\n+\t\t\tALLOC_GROW(env, env_nr + 1, env_alloc);\n+\t\t\tenv[env_nr++] = NULL;\n+\n+\t\t\tif (find_new_codelines) {\n+\t\t\t\tstruct p4_codeline *codeline;\n+\t\t\t\tsave_commit_buffer = 1;\n+\n+\t\t\t\tconnect_to_p4();\n+\n+\t\t\t\tif (!depot)\n+\t\t\t\t\tdepot = get_depot();\n+\n+\t\t\t\tfor (i = 0; i < codeline_nr; i++)\n+\t\t\t\t\timport_depot(depot,\n+\t\t\t\t\t\t     codeline_to_refname(codelines[i]));\n+\n+\t\t\t\tfor (codeline = depot->codelines; codeline;\n+\t\t\t\t     codeline = codeline->next) {\n+\t\t\t\t\tif (codeline->ignore)\n+\t\t\t\t\t\tcontinue;\n+\t\t\t\t\tfollow_codeline(codeline);\n+\t\t\t\t\tprintf(\"? %s %s\\n\", codeline->refname,\n+\t\t\t\t\t       codeline->head == codeline->history ?\n+\t\t\t\t\t       \"unchanged\" : \"\");\n+\t\t\t\t}\n+\t\t\t} else {\n+\t\t\t\tfor (i = 0; i < codeline_nr; i++)\n+\t\t\t\t\tprintf(\"? %s\\n\",\n+\t\t\t\t\t       codeline_to_refname(codelines[i]));\n+\t\t\t}\n+\t\t\tprintf(\"\\n\");\n+\t\t\tfflush(stdout);\n+\t\t} else if (!prefixcmp(buf.buf, \"export \")) {\n+\t\t\tunsigned char sha1[20];\n+\t\t\tchar *hash = buf.buf + strlen(\"export \");\n+\t\t\tchar *ref = strchr(hash, ' ');\n+\t\t\t*(ref++) = '\\0';\n+\t\t\tget_sha1(hash, sha1);\n+\n+\t\t\tconnect_to_p4();\n+\n+\t\t\tif (!depot)\n+\t\t\t\tdepot = get_depot();\n+\n+\t\t\texport_p4(depot, sha1, ref);\n+\t\t\t// 1: check whether the import of the target location\n+\t\t\t//    is up-to-date\n+\n+\t\t\t// 2: find the target location in the client view\n+\n+\t\t\t// 3: bring the client view up-to-date with the target\n+\t\t\t//    location\n+\n+\t\t\t// 4: recheck that this matches the tree\n+\n+\t\t\t// 5: open the necessary files in the client\n+\n+\t\t\t// 6: replace the necessary files in the filesystem\n+\n+\t\t\t// 7: submit\n+\n+\t\t\t// 8: reimport\n+\n+\t\t\t// 9: go back to (3)\n+\t\t} else {\n+\t\t\tdisconnect_from_p4();\n+\t\t\tfprintf(stderr, \"Unrecognized command %s\\n\", buf.buf);\n+\t\t\treturn 1;\n+\t\t}\n+\t\tstrbuf_reset(&buf);\n+\t} while (1);\n+\tdisconnect_from_p4();\n+\treturn 0;\n+}\ndiff --git a/vcs-p4/vcs-p4.h b/vcs-p4/vcs-p4.h\nnew file mode 100644\nindex 0000000..104ef90\n--- /dev/null\n+++ b/vcs-p4/vcs-p4.h\n@@ -0,0 +1,135 @@\n+#ifndef VCS_P4_H\n+#define VCS_P4_H\n+\n+struct p4_depot {\n+\tstruct p4_codeline *codelines;\n+\n+\tint next_mark;\n+\n+\tstruct p4_changeset **added;\n+\tint added_nr;\n+\tint added_alloc;\n+};\n+\n+/** Note that multiple codelines can have changesets with the same\n+ * number.\n+ **/\n+struct p4_changeset {\n+\tstruct p4_codeline *codeline;\n+\n+\tunsigned long number;\n+\n+\t/** Used only if a previous import found this changeset **/\n+\tstruct commit *commit;\n+\n+\t/** Used for the original git commit this was exported as **/\n+\tstruct commit *original;\n+\n+\t/** Used only if this changeset is newly imported in this operation. **/\n+\tint mark;\n+\n+\tconst char *message;\n+\n+\t/** These are the revisions introduced in the changeset **/\n+\tstruct p4_revision_list *revisions;\n+\n+\t/** These are the revisions which are current as of the changeset **/\n+\tstruct p4_revision_list *contents;\n+\n+\t/** Not explicit in p4 **/\n+\tstruct p4_changeset_list *integrated;\n+\n+\t/** Next and previous in codeline **/\n+\tstruct p4_changeset *next;\n+\tstruct p4_changeset *previous;\n+};\n+\n+struct p4_changeset_list {\n+\tstruct p4_changeset *changeset;\n+\tstruct p4_changeset_list *next;\n+};\n+\n+struct p4_revision {\n+\tunsigned long number;\n+\n+\tunsigned delete : 1;\n+\tunsigned branch : 1; /* unchanged against something integrated */\n+\n+\tconst char *type;\n+\n+\tstruct p4_file *file;\n+\tstruct p4_changeset *changeset;\n+\n+\tstruct p4_revision_list *integrated;\n+\n+\t/** Next in file **/\n+\tstruct p4_revision *next;\n+};\n+\n+/** Represents a collection of revisions of different files\n+ **/\n+struct p4_revision_list {\n+\tstruct p4_revision *revision;\n+\tstruct p4_revision_list *next;\n+};\n+\n+struct p4_file {\n+\tstruct p4_codeline *codeline;\n+\tconst char *name;\n+\n+\tunsigned head_number;\n+\n+\tstruct p4_revision *revisions;\n+\n+\t/** Next file in codeline **/\n+\tstruct p4_file *next;\n+};\n+\n+/** perforce doesn't record codelines; we have to reverse-engineer\n+ * them from how people seem to be branching.\n+ **/\n+struct p4_codeline {\n+\tunsigned ignore : 1;\n+\n+\tstruct p4_depot *depot;\n+\n+\t/** Base path of codeline **/\n+\tconst char *path;\n+\n+\t/** git refname to import into **/\n+\tconst char *refname;\n+\n+\tstruct p4_file *files;\n+\tstruct p4_changeset *changesets;\n+\n+\tint filelog_done;\n+\n+\t/* The incremental state is that we have some changeset that\n+\t * we previously imported up to, and we have git history going\n+\t * back from that point, of which we've looked up some and\n+\t * could look up more as needed. Also, there's p4-only history\n+\t * going forward after the common history, and we've imported\n+\t * some of that, and could import more as needed. Since\n+\t * codelines are sorted by changeset number, we can tell which\n+\t * way to go to get a name for a changeset.\n+\t */\n+\tstruct p4_changeset *history;\n+\tstruct p4_changeset *unreported;\n+\n+\tstruct p4_changeset *head;\n+\n+\tunsigned long finished_changeset;\n+\n+\t/** For reporting **/\n+\tunsigned long num_changesets;\n+\n+\t/** Next codeline in depot **/\n+\tstruct p4_codeline *next;\n+\n+\t/** Filesystem location of working directory for this codeline\n+\t * on the client.\n+\t **/\n+\tchar *working;\n+};\n+\n+#endif\n-- \n1.6.4.rc3.27.g95032.dirty\n"},{"id":"119640","messageId":"7vocqu9mzx.fsf@alter.siamese.dyndns.org","threadId":"20394","inReplyTo":"alpine.LNX.2.00.0908050056580.2147@iabervon.org","subject":"Re: [PATCH 2/2] Implement git-vcs-p4","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-08-05T17:41:38Z","receivedAt":"2009-08-05T17:41:38Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Daniel Barkalow <barkalow@iabervon.org> writes:\n\n> This is probably not particularly appropriate for mainline\n> application, and is somewhat buggy, not extensively tested, and\n> incomplete, but is here as an example implementation of the API and in\n> case it is useful to other people.\n\nI do not mind carrying this in the tree, at least on 'pu', for wider\nexposure---giving easier access to others _might_ help you gain\ncontributors.\n\nBut we need to disable this in the Makefile by default and force\ninterested parties to explicitly ask for it if we were to do that.\n"},{"id":"119647","messageId":"20090805181414.GA13166@blimp.localdomain","threadId":"20394","inReplyTo":"alpine.LNX.2.00.0908050056580.2147@iabervon.org","subject":"Re: [PATCH 2/2] Implement git-vcs-p4","fromName":"Alex Riesen","fromEmail":"raa.lkml@gmail.com","sentAt":"2009-08-05T18:14:14Z","receivedAt":"2009-08-05T18:14:14Z","isPatch":true,"sender":{"key":"raa.lkml@gmail.com","avatar":"https://avatars.githubusercontent.com/u/324101?v=4"},"body":"Daniel Barkalow, Wed, Aug 05, 2009 07:02:31 +0200:\n>  vcs-p4/p4client-api.cc       |  455 +++++++++++++++\n>  vcs-p4/p4client.c            |  158 ++++++\n>  vcs-p4/p4client.h            |   74 +++\n>  vcs-p4/vcs-p4.c              | 1250 ++++++++++++++++++++++++++++++++++++++++++\n>  vcs-p4/vcs-p4.h              |  135 +++++\n\nMaybe put these in contrib/ ?\n"},{"id":"119656","messageId":"alpine.LNX.2.00.0908051424370.2147@iabervon.org","threadId":"20394","inReplyTo":"7vocqu9mzx.fsf@alter.siamese.dyndns.org","subject":"Re: [PATCH 2/2] Implement git-vcs-p4","fromName":"Daniel Barkalow","fromEmail":"barkalow@iabervon.org","sentAt":"2009-08-05T18:32:49Z","receivedAt":"2009-08-05T18:32:49Z","isPatch":true,"sender":{"key":"barkalow@iabervon.org","avatar":"https://avatars.githubusercontent.com/u/55364219?v=4"},"body":"On Wed, 5 Aug 2009, Junio C Hamano wrote:\n\n> Daniel Barkalow <barkalow@iabervon.org> writes:\n> \n> > This is probably not particularly appropriate for mainline\n> > application, and is somewhat buggy, not extensively tested, and\n> > incomplete, but is here as an example implementation of the API and in\n> > case it is useful to other people.\n> \n> I do not mind carrying this in the tree, at least on 'pu', for wider\n> exposure---giving easier access to others _might_ help you gain\n> contributors.\n> \n> But we need to disable this in the Makefile by default and force\n> interested parties to explicitly ask for it if we were to do that.\n\nThere are two ways it can be built: either against the API library that \nPerforce distributes (without any obvious license documents) or to call \nthe client executable. The Makefile only tries to build against the API if \nconfigured to have the location of it; it currently tries the client if \nthe API wasn't configured, but I'll make it only try even that if \nconfigured to do so.\n\nAlternatively, perhaps I should set up the Makefile to have a \nconfiguration option for contrib subdirectories that need to be built? I \ndidn't put it in contrib/ because it (currently) needs to link against \n-lgit, depend on it, and have compilation rules, and everything currently \nin contrib/ is interpreted code.\n\n\t-Daniel\n*This .sig left intentionally blank*\n"},{"id":"119659","messageId":"7vws5i3xr4.fsf@alter.siamese.dyndns.org","threadId":"20394","inReplyTo":"alpine.LNX.2.00.0908051424370.2147@iabervon.org","subject":"Re: [PATCH 2/2] Implement git-vcs-p4","fromName":"Junio C Hamano","fromEmail":"gitster@pobox.com","sentAt":"2009-08-05T18:45:51Z","receivedAt":"2009-08-05T18:45:51Z","isPatch":true,"sender":{"key":"gitster@pobox.com","avatar":"https://avatars.githubusercontent.com/u/54884?v=4"},"body":"Daniel Barkalow <barkalow@iabervon.org> writes:\n\n>> But we need to disable this in the Makefile by default and force\n>> interested parties to explicitly ask for it if we were to do that.\n>\n> ... The Makefile only tries to build against the API if \n> configured to have the location of it;...\n\nAh, ok, that is exactly what I meant.  I just didn't want to suddenly\nbreak people who routinely did \"git pull && make test\".\n"}]}